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

add category to document

This commit is contained in:
Harvey Kandola 2017-09-22 17:23:14 +01:00
parent 508ec00c6a
commit f0582e18f7
19 changed files with 334 additions and 58 deletions

View file

@ -309,7 +309,112 @@ func (h *Handler) GetSummary(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, s)
}
// SetDocumentCategoryMembership will link/unlink document from categories (query string switch mode=link or mode=unlink).
func (h *Handler) SetDocumentCategoryMembership(w http.ResponseWriter, r *http.Request) {
method := "category.addMember"
ctx := domain.GetRequestContext(r)
mode := request.Query(r, "mode")
if len(mode) == 0 {
response.WriteMissingDataError(w, method, "mode")
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "body")
h.Runtime.Log.Error(method, err)
return
}
var cats []category.Member
err = json.Unmarshal(body, &cats)
if err != nil {
response.WriteBadRequestError(w, method, "category")
h.Runtime.Log.Error(method, err)
return
}
if len(cats) == 0 {
response.WriteEmpty(w)
return
}
if !permission.HasPermission(ctx, *h.Store, cats[0].LabelID, pm.DocumentAdd, pm.DocumentEdit) {
response.WriteForbiddenError(w)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
for _, c := range cats {
if mode == "link" {
c.OrgID = ctx.OrgID
c.RefID = uniqueid.Generate()
_, err = h.Store.Category.DisassociateDocument(ctx, c.CategoryID, c.DocumentID)
err = h.Store.Category.AssociateDocument(ctx, c)
} else {
_, err = h.Store.Category.DisassociateDocument(ctx, c.CategoryID, c.DocumentID)
}
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
}
h.Store.Audit.Record(ctx, audit.EventTypeCategoryLink)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// GetDocumentCategoryMembership returns categories associated with given document.
func (h *Handler) GetDocumentCategoryMembership(w http.ResponseWriter, r *http.Request) {
method := "category.GetDocumentCategoryMembership"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
doc, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error("no document for category", err)
return
}
if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.DocumentAdd, pm.DocumentEdit) {
response.WriteForbiddenError(w)
return
}
cat, err := h.Store.Category.GetDocumentCategoryMembership(ctx, doc.RefID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get document category membership", err)
response.WriteServerError(w, method, err)
return
}
if len(cat) == 0 {
cat = []category.Category{}
}
response.WriteJSON(w, cat)
}
/*
- link/unlink document to category
- filter space documents by category -- URL param? nested route?
*/

View file

@ -61,8 +61,7 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (c []catego
WHERE orgid=? AND labelid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='category' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='category' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='category'
AND p.action='view' AND r.userid=?
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='category' AND r.userid=?
))
ORDER BY category`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
@ -225,3 +224,20 @@ func (s Scope) GetSpaceCategorySummary(ctx domain.RequestContext, spaceID string
return
}
// GetDocumentCategoryMembership returns all space categories associated with given document.
func (s Scope) GetDocumentCategoryMembership(ctx domain.RequestContext, documentID string) (c []category.Category, err error) {
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, category, created, revised FROM category
WHERE orgid=? AND refid IN (SELECT categoryid FROM categorymember WHERE orgid=? AND documentid=?)`, ctx.OrgID, ctx.OrgID, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for document %s", documentID))
return
}
return
}

View file

@ -74,6 +74,7 @@ type CategoryStorer interface {
DisassociateDocument(ctx RequestContext, categoryID, documentID string) (rows int64, err error)
RemoveCategoryMembership(ctx RequestContext, categoryID string) (rows int64, err error)
DeleteBySpace(ctx RequestContext, spaceID string) (rows int64, err error)
GetDocumentCategoryMembership(ctx RequestContext, documentID string) (c []category.Category, err error)
}
// PermissionStorer defines required methods for space/document permission management

View file

@ -15,11 +15,15 @@ import NotifierMixin from '../../mixins/notifier';
export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
documentService: Ember.inject.service('document'),
sectionService: Ember.inject.service('section'),
categoryService: Ember.inject.service('category'),
sessionService: Ember.inject.service('session'),
appMeta: Ember.inject.service(),
userService: Ember.inject.service('user'),
localStorage: Ember.inject.service(),
categories: [],
hasCategories: Ember.computed('categories', function() {
return this.get('categories').length > 0;
}),
canAdd: Ember.computed('categories', function() {
return this.get('categories').length > 0 && this.get('permissions.documentEdit');
}),
init() {
this._super(...arguments);
@ -27,8 +31,62 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
didReceiveAttrs() {
this._super(...arguments);
this.load();
},
load() {
this.get('categoryService').getUserVisible(this.get('folder.id')).then((categories) => {
this.set('categories', categories);
this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => {
this.set('selectedCategories', selected);
selected.forEach((s) => {
let cats = this.set('categories', categories);
let cat = categories.findBy('id', s.id);
cat.set('selected', true);
this.set('categories', cats);
});
});
});
},
actions: {
onSave() {
let docId = this.get('document.id');
let folderId = this.get('folder.id');
let link = this.get('categories').filterBy('selected', true);
let unlink = this.get('categories').filterBy('selected', false);
let toLink = [];
let toUnlink = [];
// prepare links associated with document
link.forEach((l) => {
let t = {
folderId: folderId,
documentId: docId,
categoryId: l.get('id')
}
toLink.push(t);
});
// prepare links no longer associated with document
unlink.forEach((l) => {
let t = {
folderId: folderId,
documentId: docId,
categoryId: l.get('id')
}
toUnlink.pushObject(t);
});
this.get('categoryService').setCategoryMembership(toUnlink, 'unlink').then(() => {
this.get('categoryService').setCategoryMembership(toLink, 'link').then(() => {
this.load();
});
});
return true;
}
}
});

View file

@ -10,6 +10,7 @@
{{#layout/zone-content}}
<div id="zone-document-content" class="zone-document-content">
<div class="document-header-zone">
<div class="pull-left">
{{document/space-category document=model.document folder=model.folder folders=model.folders permissions=model.permissions}}
</div>
@ -23,13 +24,14 @@
</div>
<div class="clearfix"/>
{{#if model.document.template}}
<div class="document-template-header">Template</div>
{{/if}}
{{document/tag-editor documentTags=model.document.tags permissions=model.permissions onChange=(action 'onTagChange')}}
</div>
{{document/document-heading document=model.document permissions=model.permissions onSaveDocument=(action 'onSaveDocument')}}
{{document/tag-editor documentTags=model.document.tags permissions=model.permissions onChange=(action 'onTagChange')}}
{{#if model.document.template}}
<div class="document-template-header">Template</div>
{{/if}}
{{document/document-view
document=model.document links=model.links pages=model.pages

View file

@ -126,5 +126,22 @@ export default BaseService.extend({
}).then((response) => {
return response;
});
},
setCategoryMembership(categories, mode) {
return this.get('ajax').request(`category/member?mode=${mode}`, {
method: 'POST',
contentType: 'json',
data: JSON.stringify(categories)
});
},
// Get categories associated with a document.
getDocumentCategories(documentId) {
return this.get('ajax').request(`category/document/${documentId}`, {
method: 'GET'
}).then((response) => {
return response;
});
}
});

View file

@ -6,7 +6,6 @@
font-size: 1.2rem;
color: $color-gray;
}
> .normal-state {
margin: 10px;
font-size: 1.2rem;
@ -17,13 +16,4 @@
.back-to-space {
margin: 0 0 10px 0;
display: inline-block;
> a {
> .regular-button {
> .name {
// max-width: 150px;
// @extend .truncate;
}
}
}
}

View file

@ -7,3 +7,4 @@
@import "toc.scss";
@import "view.scss";
@import "wysiwyg.scss";
@import "space-category-tag.scss";

View file

@ -1,5 +1,10 @@
.document-attachments {
margin: 0;
margin: 0 0 50px 0;
// @include content-container();
> h2 {
color: $color-gray;
}
> .upload-document-files {
margin: 10px 0 0 0;

View file

@ -0,0 +1,53 @@
.document-space {
display: inline-block;
> .caption {
text-transform: uppercase;
color: $color-gray;
font-weight: bold;
font-size: 1rem;
margin: 0 0 10px 0;
}
}
.document-category {
display: inline-block;
margin: 0 0 0 30px;
> .regular-button {
margin-right: 10px;
}
> .caption {
text-transform: uppercase;
color: $color-gray;
font-weight: bold;
font-size: 1rem;
margin: 0 0 10px 0;
}
}
.document-tags {
margin: 20px 0 0 0;
> .caption {
text-transform: uppercase;
color: $color-gray;
font-weight: bold;
font-size: 1rem;
margin: 0 0 10px 0;
}
> .regular-button {
margin-right: 10px;
&:hover {
visibility: visible;
}
> .material-icons {
visibility: hidden;
}
}
}

View file

@ -1,18 +1,25 @@
.zone-document-content {
> .document-header-zone {
padding: 20px 30px;
border: 1px solid $color-stroke;
@include border-radius(3px);
background-color: $color-off-white;
}
.doc-title {
font-size: 2rem;
margin: 30px 0 10px;
margin: 50px 0 10px;
font-weight: normal;
}
.doc-excerpt {
font-size: 1rem;
color: $color-gray;
margin: 0 0 60px;
margin: 0 0 45px;
}
.edit-document-heading {
margin: 30px 0 0 0;
margin: 50px 0 0 0;
.edit-doc-title {
> input {
@ -32,7 +39,7 @@
}
.document-view {
margin: 0 0 50px 0;
margin: 0 0 0 0;
.is-a-page {
@include content-container();
@ -359,7 +366,3 @@
font-size: 1.5em;
margin-bottom: 20px;
}
.document-tags {
margin-top: 15px;
}

View file

@ -242,6 +242,13 @@
border: 1px solid $color-gray;
}
.button-chip {
background-color: $color-chip;
color: $color-chip-text;
border: 1px solid $color-chip-border;
@include button-hover-state($color-chip);
}
.flat-button {
box-shadow: none;
background-color: transparent;

View file

@ -1,22 +1,24 @@
.chip {
display: inline-block;
border-radius: 3px;
border: 1px solid $color-chip-border;
padding: 0;
height: 25px;
line-height: 0;
margin: 0 5px 10px 0;
border: 1px solid $color-chip-border;
background-color: $color-chip;
color: $color-chip-text;
&:hover {
cursor: pointer;
> i.material-icons {
visibility: visible;
}
}
> .chip-text {
display: inline-block;
font-weight: 400;
font-size: 12px;
font-size: 1rem;
color: $color-chip-text;
padding: 11px 10px 0 10px;
letter-spacing: 0.7px;
@ -24,6 +26,7 @@
}
> i.material-icons {
visibility: hidden;
color: $color-chip-text;
font-size: 13px;
margin: 13px 8px 0 0;

View file

@ -1,4 +1,5 @@
<div class="document-attachments">
<h2>Attachments</h2>
{{#if hasAttachments}}
<ul class="list">
{{#each files key="id" as |a index|}}

View file

@ -1,5 +1,3 @@
{{document/document-attachments document=document permissions=permissions}}
<div class="document-view {{if (is-equal document.layout 'doc') 'document-view-unified'}}">
{{#if hasPages}}
@ -103,3 +101,5 @@
</div>
</div>
{{document/document-attachments document=document permissions=permissions}}

View file

@ -1,4 +1,5 @@
<div class="back-to-space">
<div class="back-to-space document-space">
<div class="caption">Space</div>
{{#link-to 'folder' folder.id folder.slug}}
<div class="regular-button button-gray">
<i class="material-icons">arrow_back</i>
@ -7,8 +8,23 @@
{{/link-to}}
</div>
<div class="document-category hide">
<div class="chip chip-action">
<span id="upload-document-files" class="chip-text">+ category</span>
<div class="document-category">
<div class="caption">Category</div>
{{#each selectedCategories as |cat|}}
<div class="regular-button button-blue">{{cat.category}}</div>
{{/each}}
{{#if hasCategories}}
<div class="regular-button button-white" id="document-category-button">
<i class="material-icons">add</i>
</div>
{{#dropdown-dialog target="document-category-button" position="bottom left" button="set" color="flat-green" onAction=(action 'onSave')}}
<p class="heading">Select categories for document</p>
{{ui/ui-list-picker items=categories nameField='category'}}
{{/dropdown-dialog}}
{{else}}
<p>&nbsp;</p>
{{/if}}
</div>

View file

@ -1,15 +1,11 @@
<div class="document-tags">
<div class="caption">Tag</div>
{{#each tagz as |tg|}}
<div class="chip">
<span class="chip-text">#{{tg}}</span>
{{#if permissions.documentEdit}}
<i class="material-icons pull-right" {{action 'removeTag' tg}}>close</i>
{{/if}}
</div>
<div class="regular-button button-chip">{{concat '#' tg}}</div>
{{/each}}
{{#if canAdd}}
<div class="chip-action">
<span id="add-tag-button" class="chip-text">+ tag</span>
<span id="add-tag-button" class="chip-text">+</span>
</div>
{{#dropdown-dialog target="add-tag-button" position="bottom left" button="Add" color="flat-green" onAction=(action 'addTag') focusOn="add-tag-field" onOpenCallback=(action 'onTagEditor') targetOffset="20px 0"}}
<div class="input-control">

View file

@ -68,7 +68,7 @@
</div>
<div class="actions">
<div class="flat-button" {{action 'onGrantCancel'}}>cancel</div>
<div class="flat-button flat-blue" {{action 'onGrantAccess'}}>grant access</div>
<div class="flat-button flat-blue" {{action 'onGrantAccess'}}>set access</div>
</div>
<div class="clearfix"></div>
</div>

View file

@ -127,6 +127,7 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"GET", "OPTIONS"}, nil, space.Get)
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"PUT", "OPTIONS"}, nil, space.Update)
Add(rt, RoutePrefixPrivate, "category/document/{documentID}", []string{"GET", "OPTIONS"}, nil, category.GetDocumentCategoryMembership)
Add(rt, RoutePrefixPrivate, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, []string{"filter", "all"}, category.GetAll)
Add(rt, RoutePrefixPrivate, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.Get)
Add(rt, RoutePrefixPrivate, "category", []string{"POST", "OPTIONS"}, nil, category.Add)
@ -136,6 +137,7 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"PUT", "OPTIONS"}, nil, permission.SetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/user", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryViewers)
Add(rt, RoutePrefixPrivate, "category/member", []string{"POST", "OPTIONS"}, nil, category.SetDocumentCategoryMembership)
Add(rt, RoutePrefixPrivate, "users/{userID}/password", []string{"POST", "OPTIONS"}, nil, user.ChangePassword)
Add(rt, RoutePrefixPrivate, "users", []string{"POST", "OPTIONS"}, nil, user.Add)