1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 13:19:43 +02:00

search results for links

This commit is contained in:
Harvey Kandola 2016-10-27 13:40:54 -07:00
parent 899b4f978c
commit ad716a23ba
12 changed files with 390 additions and 69 deletions

View file

@ -21,6 +21,11 @@ export default Ember.Component.extend(TooltipMixin, {
linkName: '',
keywords: '',
selection: null,
matches: {
documents: [],
pages: [],
attachments: []
},
tabs: [
{ label: 'Section', selected: true },
{ label: 'Attachment', selected: false },
@ -36,6 +41,11 @@ export default Ember.Component.extend(TooltipMixin, {
showSearch: Ember.computed('tabs.@each.selected', function() {
return this.get('tabs').findBy('label', 'Search').selected;
}),
hasMatches: Ember.computed('matches', function() {
let m = this.get('matches');
return m.documents.length || m.pages.length || m.attachments.length;
}),
init() {
this._super(...arguments);
@ -60,11 +70,30 @@ export default Ember.Component.extend(TooltipMixin, {
this.destroyTooltips();
},
onKeywordChange: function () {
Ember.run.debounce(this, this.fetch, 750);
}.observes('keywords'),
fetch() {
let keywords = this.get('keywords');
let self = this;
if (_.isEmpty(keywords)) {
this.set('matches', { documents: [], pages: [], attachments: [] });
return;
}
this.get('link').searchCandidates(keywords).then(function (matches) {
self.set('matches', matches);
});
},
actions: {
setSelection(i) {
this.set('selection', i);
let candidates = this.get('candidates');
let matches = this.get('matches');
this.set('selection', i);
candidates.pages.forEach(c => {
Ember.set(c, 'selected', c.id === i.id);
@ -73,6 +102,18 @@ export default Ember.Component.extend(TooltipMixin, {
candidates.attachments.forEach(c => {
Ember.set(c, 'selected', c.id === i.id);
});
matches.documents.forEach(c => {
Ember.set(c, 'selected', c.id === i.id);
});
matches.pages.forEach(c => {
Ember.set(c, 'selected', c.id === i.id);
});
matches.attachments.forEach(c => {
Ember.set(c, 'selected', c.id === i.id);
});
},
onInsertLink() {

View file

@ -0,0 +1,15 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
export default Ember.Component.extend({
});

View file

@ -22,7 +22,7 @@ export default Ember.Service.extend({
store: service(),
// Returns candidate links using provided parameters
getCandidates(folderId, documentId, pageId /*, keywords*/ ) {
getCandidates(folderId, documentId, pageId) {
return this.get('ajax').request(`links/${folderId}/${documentId}/${pageId}`, {
method: 'GET'
}).then((response) => {
@ -30,6 +30,27 @@ export default Ember.Service.extend({
});
},
// Returns keyword-based candidates
searchCandidates(keywords) {
let url = "links?keywords=" + encodeURIComponent(keywords);
return this.get('ajax').request(url, {
method: 'GET'
}).then((response) => {
return response;
});
},
// getUsers returns all users for organization.
find(keywords) {
let url = "search?keywords=" + encodeURIComponent(keywords);
return this.get('ajax').request(url, {
method: "GET"
});
},
buildLink(link) {
let result = "";
let href = "";
@ -45,7 +66,6 @@ export default Ember.Service.extend({
result = `<a data-documize='true' data-link-space-id='${link.folderId}' data-link-id='${link.id}' data-link-document-id='${link.documentId}' data-link-target-id='${link.targetId}' data-link-type='${link.linkType}' href='${href}'>${link.title}</a>`;
}
return result;
},
@ -85,7 +105,7 @@ export default Ember.Service.extend({
}
// handle document link
if (link.inkType === "document") {
if (link.linkType === "document") {
router.transitionTo('document', link.folderId, folderSlug, link.documentId, documentSlug);
return;
}
@ -99,7 +119,6 @@ export default Ember.Service.extend({
});
/*
Keyword search results - docs, section, files
The link id's get ZERO'd in Page.Body whenever:
- doc is moved to different space

View file

@ -1,11 +1,11 @@
.edit-tools {
margin: 0 0 0 20px;
min-height: 500px;
min-height: 600px;
}
.content-linker-dialog {
width: 350px;
height: 400px;
height: 500px;
overflow-y: auto;
.link-list {
@ -14,7 +14,7 @@
.link-item {
margin: 0;
padding: 2px 0;
padding: 0;
font-size: 0.9rem;
color: $color-gray;
cursor: pointer;

View file

@ -0,0 +1,34 @@
.widget-selection {
> .option {
width: 100%;
margin: 0;
padding: 5px 10px;
text-align: left;
@extend .no-select;
cursor: pointer;
// border: 1px solid $color-border;
color: $color-off-black;
position: relative;
> i.material-icons {
display: none;
}
}
&:hover {
@include ease-in();
background-color: $color-off-white;
}
> .selected {
background-color: $color-card-active !important;
color: $color-primary !important;
> i.material-icons {
display: inline-block;
position: absolute;
right: 10px;
top: 5px;
}
}
}

View file

@ -1,27 +1,29 @@
.widget-tab {
width: 100%;
margin: 0;
padding: 0 5px;
padding: 0 10px;
text-align: center;
border-bottom: 1px solid $color-border;
@extend .no-select;
> .tab {
display: inline-block;
margin: 0;
padding: 5px 10px;
color: $color-off-black;
background-color: $color-off-white;
color: $color-gray;
text-align: center;
cursor: pointer;
@include ease-in();
margin-right: -3px;
&:hover {
background-color: $color-off-white;
color: $color-link;
@include ease-in();
background-color: $color-gray;
color: $color-off-white;
}
}
> .selected {
background-color: $color-off-white;
color: $color-link;
background-color: $color-gray;
color: $color-off-white;
}
}

View file

@ -60,6 +60,7 @@
.z-depth-5 {
box-shadow: 0 27px 24px 0 rgba(0, 0, 0, 0.2), 0 40px 77px 0 rgba(0, 0, 0, 0.22);
}
@import "widget-avatar";
@import "widget-button";
@import "widget-card";
@ -72,3 +73,4 @@
@import "widget-tooltip";
@import "widget-checkbox";
@import "widget-tab";
@import "widget-selection";

View file

@ -15,9 +15,9 @@
<ul class="link-list">
{{#each candidates.pages as |p|}}
<li class="link-item" {{ action 'setSelection' p }}>
{{#ui/ui-checkbox selected=p.selected}}
{{#ui/ui-selection selected=p.selected}}
{{p.title}}
{{/ui/ui-checkbox}}
{{/ui/ui-selection}}
</li>
{{/each}}
</ul>
@ -27,10 +27,10 @@
<ul class="link-list">
{{#each candidates.attachments as |a|}}
<li class="link-item" {{ action 'setSelection' a }}>
{{#ui/ui-checkbox selected=a.selected}}
{{#ui/ui-selection selected=a.selected}}
<img class="icon" src="/assets/img/attachments/{{document/file-icon a.context}}" />
{{ a.title }}
{{/ui/ui-checkbox}}
{{/ui/ui-selection}}
</li>
{{/each}}
</ul>
@ -39,9 +39,36 @@
{{#if showSearch}}
<div class="input-control">
<label>Search</label>
<div class="tip">keywords</div>
<div class="tip">For content or attachments</div>
{{focus-input id="content-linker-search" type="input" value=keywords placeholder="keyword search"}}
</div>
{{#unless hasMatches}}
Nothing found.
{{/unless}}
<ul class="link-list">
{{#each matches.documents as |m|}}
<li class="link-item" {{ action 'setSelection' m }}>
{{#ui/ui-selection selected=m.selected}}
{{m.title}}
{{/ui/ui-selection}}
</li>
{{/each}}
{{#each matches.pages as |m|}}
<li class="link-item" {{ action 'setSelection' m }}>
{{#ui/ui-selection selected=m.selected}}
{{m.title}}<br/><span class="color-gray">{{m.context}}</span>
{{/ui/ui-selection}}
</li>
{{/each}}
{{#each matches.attachments as |a|}}
<li class="link-item" {{ action 'setSelection' a }}>
{{#ui/ui-selection selected=a.selected}}
<img class="icon" src="/assets/img/attachments/{{document/file-icon a.context}}" />
{{ a.title }}
{{/ui/ui-selection}}
</li>
{{/each}}
</ul>
{{/if}}
<div class="hide regular-button button-blue pull-right" {{ action 'onInsertLink' }}>Insert</div>

View file

@ -0,0 +1,6 @@
<div class="widget-selection">
<div class="option {{if selected 'selected'}}">
{{yield}}
<i class="material-icons">check</i>
</div>
</div>

View file

@ -15,12 +15,14 @@ import (
"database/sql"
"encoding/json"
"net/http"
"net/url"
"github.com/gorilla/mux"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
)
// GetLinkCandidates returns references to documents/sections/attachments.
@ -112,7 +114,6 @@ func GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
var payload struct {
Pages []entity.LinkCandidate `json:"pages"`
Attachments []entity.LinkCandidate `json:"attachments"`
Matches []entity.LinkCandidate `json:"matches"`
}
payload.Pages = pc
@ -127,3 +128,40 @@ func GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
util.WriteSuccessBytes(w, json)
}
// SearchLinkCandidates endpoint takes a list of keywords and returns a list of document references matching those keywords.
func SearchLinkCandidates(w http.ResponseWriter, r *http.Request) {
method := "SearchLinkCandidates"
p := request.GetPersister(r)
query := r.URL.Query()
keywords := query.Get("keywords")
decoded, err := url.QueryUnescape(keywords)
log.IfErr(err)
docs, pages, attachments, err := p.SearchLinkCandidates(decoded)
if err != nil {
util.WriteServerError(w, method, err)
return
}
var payload struct {
Documents []entity.LinkCandidate `json:"documents"`
Pages []entity.LinkCandidate `json:"pages"`
Attachments []entity.LinkCandidate `json:"attachments"`
}
payload.Documents = docs
payload.Pages = pages
payload.Attachments = attachments
json, err := json.Marshal(payload)
if err != nil {
util.WriteMarshalError(w, err)
return
}
util.WriteSuccessBytes(w, json)
}

View file

@ -214,6 +214,7 @@ func init() {
// Links
log.IfErr(Add(RoutePrefixPrivate, "links/{folderID}/{documentID}/{pageID}", []string{"GET", "OPTIONS"}, nil, GetLinkCandidates))
log.IfErr(Add(RoutePrefixPrivate, "links", []string{"GET", "OPTIONS"}, nil, SearchLinkCandidates))
// Global installation-wide config
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"GET", "OPTIONS"}, nil, GetGlobalConfig))

View file

@ -16,6 +16,7 @@ import (
"time"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
@ -45,53 +46,188 @@ func (p *Persister) AddContentLink(l entity.Link) (err error) {
return
}
// SearchLinkCandidates returns matching documents, sections and attachments using keywords.
func (p *Persister) SearchLinkCandidates(keywords string) (docs []entity.LinkCandidate,
pages []entity.LinkCandidate, attachments []entity.LinkCandidate, err error) {
err = nil
// find matching documents
temp := []entity.LinkCandidate{}
likeQuery := "title LIKE '%" + keywords + "%'"
err = Db.Select(&temp,
`SELECT refid as documentid, labelid as folderid,title from document WHERE orgid=? AND `+likeQuery+` AND labelid IN
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
ORDER BY title`,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search links for org %s", p.Context.OrgID), err)
return
}
for _, r := range temp {
c := entity.LinkCandidate{
RefID: util.UniqueID(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.DocumentID,
LinkType: "document",
Title: r.Title,
Context: "",
}
docs = append(docs, c)
}
// find matching sections
likeQuery = "p.title LIKE '%" + keywords + "%'"
temp = []entity.LinkCandidate{}
err = Db.Select(&temp,
`SELECT p.refid as targetid, p.documentid as documentid, p.title as title, d.title as context, d.labelid as folderid from page p
LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+` AND d.labelid IN
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
ORDER BY p.title`,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search links for org %s", p.Context.OrgID), err)
return
}
for _, r := range temp {
c := entity.LinkCandidate{
RefID: util.UniqueID(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.TargetID,
LinkType: "section",
Title: r.Title,
Context: r.Context,
}
pages = append(pages, c)
}
// find matching attachments
likeQuery = "a.filename LIKE '%" + keywords + "%'"
temp = []entity.LinkCandidate{}
err = Db.Select(&temp,
`SELECT a.refid as targetid, a.documentid as documentid, a.filename as title, a.extension as context, d.labelid as folderid from attachment a
LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+` AND d.labelid IN
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
ORDER BY a.filename`,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search links for org %s", p.Context.OrgID), err)
return
}
for _, r := range temp {
c := entity.LinkCandidate{
RefID: util.UniqueID(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.TargetID,
LinkType: "file",
Title: r.Title,
Context: r.Context,
}
attachments = append(attachments, c)
}
if len(docs) == 0 {
docs = []entity.LinkCandidate{}
}
if len(pages) == 0 {
pages = []entity.LinkCandidate{}
}
if len(attachments) == 0 {
attachments = []entity.LinkCandidate{}
}
return
}
// GetReferencedLinks returns all links that the specified section is referencing.
func (p *Persister) GetReferencedLinks(sectionID string) (links []entity.Link, err error) {
err = nil
sql := "SELECT id,refid,orgid,folderid,userid,sourceid,documentid,targetid,linktype,orphan,created,revised from link WHERE orgid=? AND sourceid=?"
err = Db.Select(&links, sql, p.Context.OrgID, sectionID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select links for org %s", p.Context.OrgID), err)
return
}
return
}
// GetContentLinksForSection returns all links that are linking to the specified section.
func (p *Persister) GetContentLinksForSection(sectionID string) (links []entity.Link, err error) {
err = nil
sql := "SELECT id,refid,orgid,folderid,userid,sourceid,documentid,targetid,linktype,orphan,created,revised from link WHERE orgid=? AND sectionid=?"
err = Db.Select(&links, sql, p.Context.OrgID, sectionID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select links for org %s", p.Context.OrgID), err)
return
}
return
}
// GetContentLinksForDocument returns all links that are linking to the specified document.
func (p *Persister) GetContentLinksForDocument(documentID string) (links []entity.Link, err error) {
err = nil
sql := "SELECT id,refid,orgid,folderid,userid,sourceid,documentid,targetid,linktype,orphan,created,revised from link WHERE orgid=? AND documentid=?"
err = Db.Select(&links, sql, p.Context.OrgID, documentID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select links for org %s", p.Context.OrgID), err)
return
}
return
}
// func (p *Persister) GetReferencedLinks(sectionID string) (links []entity.Link, err error) {
// err = nil
//
// sql := "SELECT id,refid,orgid,folderid,userid,sourceid,documentid,targetid,linktype,orphan,created,revised from link WHERE orgid=? AND sourceid=?"
//
// err = Db.Select(&links, sql, p.Context.OrgID, sectionID)
//
// if err != nil {
// log.Error(fmt.Sprintf("Unable to execute select links for org %s", p.Context.OrgID), err)
// return
// }
//
// return
// }
//
// // GetContentLinksForSection returns all links that are linking to the specified section.
// func (p *Persister) GetContentLinksForSection(sectionID string) (links []entity.Link, err error) {
// err = nil
//
// sql := "SELECT id,refid,orgid,folderid,userid,sourceid,documentid,targetid,linktype,orphan,created,revised from link WHERE orgid=? AND sectionid=?"
//
// err = Db.Select(&links, sql, p.Context.OrgID, sectionID)
//
// if err != nil {
// log.Error(fmt.Sprintf("Unable to execute select links for org %s", p.Context.OrgID), err)
// return
// }
//
// return
// }
//
// // GetContentLinksForDocument returns all links that are linking to the specified document.
// func (p *Persister) GetContentLinksForDocument(documentID string) (links []entity.Link, err error) {
// err = nil
//
// sql := "SELECT id,refid,orgid,folderid,userid,sourceid,documentid,targetid,linktype,orphan,created,revised from link WHERE orgid=? AND documentid=?"
//
// err = Db.Select(&links, sql, p.Context.OrgID, documentID)
//
// if err != nil {
// log.Error(fmt.Sprintf("Unable to execute select links for org %s", p.Context.OrgID), err)
// return
// }
//
// return
// }
// MarkOrphanContentLink marks the link record as being invalid.
func (p *Persister) MarkOrphanContentLink(l entity.Link) (err error) {