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

document list: show by selected space/category

This commit is contained in:
Harvey Kandola 2017-09-26 16:30:16 +01:00
parent 2cee83d570
commit 5481de4e1c
21 changed files with 342 additions and 157 deletions

View file

@ -415,6 +415,32 @@ func (h *Handler) GetDocumentCategoryMembership(w http.ResponseWriter, r *http.R
response.WriteJSON(w, cat) response.WriteJSON(w, cat)
} }
/* // GetSpaceCategoryMembers returns category/document associations within space.
- filter space documents by category -- URL param? nested route? func (h *Handler) GetSpaceCategoryMembers(w http.ResponseWriter, r *http.Request) {
*/ method := "category.GetSpaceCategoryMembers"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
if !permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceView) {
response.WriteForbiddenError(w)
return
}
cat, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get document category membership for space", err)
response.WriteServerError(w, method, err)
return
}
if len(cat) == 0 {
cat = []category.Member{}
}
response.WriteJSON(w, cat)
}

View file

@ -45,7 +45,7 @@ func (s Scope) Add(ctx domain.RequestContext, c category.Category) (err error) {
return return
} }
// GetBySpace returns space categories for a user. // GetBySpace returns space categories accessible by user.
// Context is used to for user ID. // Context is used to for user ID.
func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) { func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
err = s.Runtime.Db.Select(&c, ` err = s.Runtime.Db.Select(&c, `
@ -185,7 +185,7 @@ func (s Scope) GetSpaceCategorySummary(ctx domain.RequestContext, spaceID string
err = nil err = nil
} }
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select category summary for space %s", spaceID)) err = errors.Wrap(err, fmt.Sprintf("select category summary for space %s", spaceID))
} }
return return
@ -206,3 +206,25 @@ func (s Scope) GetDocumentCategoryMembership(ctx domain.RequestContext, document
return return
} }
// GetSpaceCategoryMembership returns category/document associations within space.
func (s Scope) GetSpaceCategoryMembership(ctx domain.RequestContext, spaceID string) (c []category.Member, err error) {
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, categoryid, documentid, created, revised FROM categorymember
WHERE orgid=? AND labelid=?
AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space' 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='space'
AND p.action='view' AND r.userid=?
))
ORDER BY documentid`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select all category/document membership for space %s", spaceID))
}
return
}

View file

@ -137,11 +137,10 @@ func (h *Handler) DocumentLinks(w http.ResponseWriter, r *http.Request) {
// BySpace is an endpoint that returns the documents for given space. // BySpace is an endpoint that returns the documents for given space.
func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) { func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
method := "document.space" method := "document.BySpace"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
spaceID := request.Query(r, "space") spaceID := request.Query(r, "space")
if len(spaceID) == 0 { if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "space") response.WriteMissingDataError(w, method, "space")
return return
@ -152,45 +151,47 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
return return
} }
// get complete list of documents
documents, err := h.Store.Document.GetBySpace(ctx, spaceID) documents, err := h.Store.Document.GetBySpace(ctx, spaceID)
if len(documents) == 0 {
documents = []doc.Document{}
}
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
return return
} }
response.WriteJSON(w, documents)
}
// ByTag is an endpoint that returns the documents with a given tag.
func (h *Handler) ByTag(w http.ResponseWriter, r *http.Request) {
method := "document.space"
ctx := domain.GetRequestContext(r)
tag := request.Query(r, "tag")
if len(tag) == 0 {
response.WriteMissingDataError(w, method, "tag")
return
}
documents, err := h.Store.Document.GetByTag(ctx, tag)
if len(documents) == 0 { if len(documents) == 0 {
documents = []doc.Document{} documents = []doc.Document{}
} }
if err != nil && err != sql.ErrNoRows { // remove documents that cannot be seen due to lack of
response.WriteServerError(w, method, err) // category view/access permission
h.Runtime.Log.Error(method, err) filtered := []doc.Document{}
return cats, err := h.Store.Category.GetBySpace(ctx, spaceID)
members, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
for _, doc := range documents {
hasCategory := false
canSeeCategory := false
OUTER:
for _, m := range members {
if m.DocumentID == doc.RefID {
hasCategory = true
for _, cat := range cats {
if cat.RefID == m.CategoryID {
canSeeCategory = true
continue OUTER
}
}
}
}
if !hasCategory || canSeeCategory {
filtered = append(filtered, doc)
}
} }
response.WriteJSON(w, documents) response.WriteJSON(w, filtered)
} }
// Update updates an existing document using the // Update updates an existing document using the

View file

@ -101,9 +101,22 @@ func (s Scope) GetAll() (ctx domain.RequestContext, documents []doc.Document, er
return return
} }
// GetBySpace returns a slice containing the documents for a given space, most recient first. // GetBySpace returns a slice containing the documents for a given space.
func (s Scope) GetBySpace(ctx domain.RequestContext, folderID string) (documents []doc.Document, err error) { // No attempt is made to hide documents that are protected
err = s.Runtime.Db.Select(&documents, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=0 AND labelid=? ORDER BY revised DESC", ctx.OrgID, folderID) // by category permissions -- caller must filter as required.
func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, `
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised
FROM document
WHERE orgid=? AND template=0 AND labelid IN (
SELECT refid FROM label WHERE orgid=? AND refid IN
(SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid=? AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space' 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='space' AND p.refid=? AND p.action='view' AND r.userid=?
))
)
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
if err != nil { if err != nil {
err = errors.Wrap(err, "select documents by space") err = errors.Wrap(err, "select documents by space")
@ -112,31 +125,6 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, folderID string) (documents
return return
} }
// GetByTag returns a slice containing the documents with the specified tag, in title order.
func (s Scope) GetByTag(ctx domain.RequestContext, tag string) (documents []doc.Document, err error) {
tagQuery := "tags LIKE '%#" + tag + "#%'"
err = s.Runtime.Db.Select(&documents, `
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=0 AND `+tagQuery+`
AND labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space'
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='space' AND p.action='view' AND r.userid=?
))
)
ORDER BY title
`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "select documents by tag")
}
return
}
// Templates returns a slice containing the documents available as templates to the client's organisation, in title order. // Templates returns a slice containing the documents available as templates to the client's organisation, in title order.
func (s Scope) Templates(ctx domain.RequestContext) (documents []doc.Document, err error) { func (s Scope) Templates(ctx domain.RequestContext) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, err = s.Runtime.Db.Select(&documents,

View file

@ -811,4 +811,4 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
ctx.Transaction.Commit() ctx.Transaction.Commit()
response.WriteEmpty(w) response.WriteEmpty(w)
} }

View file

@ -75,6 +75,7 @@ type CategoryStorer interface {
RemoveCategoryMembership(ctx RequestContext, categoryID string) (rows int64, err error) RemoveCategoryMembership(ctx RequestContext, categoryID string) (rows int64, err error)
DeleteBySpace(ctx RequestContext, spaceID string) (rows int64, err error) DeleteBySpace(ctx RequestContext, spaceID string) (rows int64, err error)
GetDocumentCategoryMembership(ctx RequestContext, documentID string) (c []category.Category, err error) GetDocumentCategoryMembership(ctx RequestContext, documentID string) (c []category.Category, err error)
GetSpaceCategoryMembership(ctx RequestContext, spaceID string) (c []category.Member, err error)
} }
// PermissionStorer defines required methods for space/document permission management // PermissionStorer defines required methods for space/document permission management
@ -157,8 +158,7 @@ type DocumentStorer interface {
Add(ctx RequestContext, document doc.Document) (err error) Add(ctx RequestContext, document doc.Document) (err error)
Get(ctx RequestContext, id string) (document doc.Document, err error) Get(ctx RequestContext, id string) (document doc.Document, err error)
GetAll() (ctx RequestContext, documents []doc.Document, err error) GetAll() (ctx RequestContext, documents []doc.Document, err error)
GetBySpace(ctx RequestContext, folderID string) (documents []doc.Document, err error) GetBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error)
GetByTag(ctx RequestContext, tag string) (documents []doc.Document, err error)
DocumentList(ctx RequestContext) (documents []doc.Document, err error) DocumentList(ctx RequestContext) (documents []doc.Document, err error)
Templates(ctx RequestContext) (documents []doc.Document, err error) Templates(ctx RequestContext) (documents []doc.Document, err error)
TemplatesBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error) TemplatesBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error)

View file

@ -12,15 +12,6 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Component.extend({ export default Ember.Component.extend({
folderService: Ember.inject.service('folder'),
moveTarget: null,
didReceiveAttrs() {
this._super(...arguments);
this.set('deleteTargets', this.get('folders').rejectBy('id', this.get('folder.id')));
},
actions: { actions: {
selectDocument(documentId) { selectDocument(documentId) {
let doc = this.get('documents').findBy('id', documentId); let doc = this.get('documents').findBy('id', documentId);

View file

@ -26,6 +26,54 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
selectedDocuments: [], selectedDocuments: [],
hasSelectedDocuments: Ember.computed.gt('selectedDocuments.length', 0), hasSelectedDocuments: Ember.computed.gt('selectedDocuments.length', 0),
showStartDocument: false, showStartDocument: false,
filteredDocs: [],
selectedCategory: '',
didReceiveAttrs() {
this._super(...arguments);
let categories = this.get('categories');
let categorySummary = this.get('categorySummary');
let selectedCategory = '';
categories.forEach((cat)=> {
let summary = _.findWhere(categorySummary, {type: "documents", categoryId: cat.get('id')});
let docCount = is.not.undefined(summary) ? summary.count : 0;
cat.set('docCount', docCount);
if (docCount > 0 && selectedCategory === '') selectedCategory = cat.get('id');
});
this.set('categories', categories);
this.set('selectedCategory', selectedCategory);
// Default document view logic:
//
// 1. show space root documents if we have any
// 2. show category documents for first category that has documents
if (this.get('rootDocCount') > 0) {
this.send('onDocumentFilter', 'space', this.get('folder.id'));
} else {
if (selectedCategory !== '') {
this.send('onDocumentFilter', 'category', selectedCategory);
}
}
},
didRender() {
this._super(...arguments);
if (this.get('categories.length') > 0) {
this.addTooltip(document.getElementById("uncategorized-button"));
}
},
willDestroyElement() {
this._super(...arguments);
if (this.get('isDestroyed') || this.get('isDestroying')) return;
this.destroyTooltips();
},
actions: { actions: {
onMoveDocument(folder) { onMoveDocument(folder) {
@ -86,6 +134,36 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
onHideStartDocument() { onHideStartDocument() {
this.set('showStartDocument', false); this.set('showStartDocument', false);
},
onDocumentFilter(filter, id) {
let docs = this.get('documents');
let categoryMembers = this.get('categoryMembers');
let filtered = [];
// filter doc list by category
if (filter === 'category') {
let allowed = _.pluck(_.where(categoryMembers, {'categoryId': id}), 'documentId');
docs.forEach((d) => {
if (_.contains(allowed, d.get('id'))) {
filtered.pushObject(d);
}
});
}
// filter doc list by space (i.e. have no category)
if (filter === 'space') {
this.set('selectedCategory', id);
let allowed = _.pluck(categoryMembers, 'documentId');
docs.forEach((d) => {
if (!_.contains(allowed, d.get('id'))) {
filtered.pushObject(d);
}
});
}
this.set('filteredDocs', filtered);
this.set('selectedCategory', id);
} }
} }
}); });

View file

@ -13,6 +13,8 @@ import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, { export default Ember.Route.extend(AuthenticatedRouteMixin, {
categoryService: Ember.inject.service('category'),
model() { model() {
this.get('browser').setTitle(this.modelFor('folder').folder.get('name')); this.get('browser').setTitle(this.modelFor('folder').folder.get('name'));
@ -23,9 +25,29 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
documents: this.modelFor('folder').documents, documents: this.modelFor('folder').documents,
templates: this.modelFor('folder').templates, templates: this.modelFor('folder').templates,
showStartDocument: false, showStartDocument: false,
categories: this.get('categoryService').getUserVisible(this.modelFor('folder').folder.get('id')),
categorySummary: this.get('categoryService').getSummary(this.modelFor('folder').folder.get('id')),
categoryMembers: this.get('categoryService').getSpaceCategoryMembership(this.modelFor('folder').folder.get('id')),
rootDocCount: 0
}); });
}, },
afterModel(model, transition) { // eslint-disable-line no-unused-vars
let docs = model.documents;
let categoryMembers = model.categoryMembers;
let rootDocCount = 0;
// get documentId's from category members
let withCat = _.pluck(categoryMembers, 'documentId');
// calculate documents without category;
docs.forEach((d) => {
if (!withCat.includes(d.get('id'))) rootDocCount+=1;
});
model.rootDocCount = rootDocCount;
},
activate() { activate() {
this.set('model.showStartDocument', false); this.set('model.showStartDocument', false);
} }

View file

@ -1,20 +1,26 @@
{{#layout/zone-container}} {{#layout/zone-container}}
{{#layout/zone-sidebar}} {{#layout/zone-sidebar}}
{{folder/sidebar-zone folders=model.folders folder=model.folder {{folder/sidebar-zone
permissions=model.permissions tab=tab onAddSpace=(action 'onAddSpace')}} folders=model.folders
folder=model.folder
permissions=model.permissions
tab=tab
onAddSpace=(action 'onAddSpace')}}
{{/layout/zone-sidebar}} {{/layout/zone-sidebar}}
{{#layout/zone-content}} {{#layout/zone-content}}
{{folder/space-view
{{folder/space-view folders=model.folders
folders=model.folders folder=model.folder
folder=model.folder templates=model.templates
templates=model.templates permissions=model.permissions
permissions=model.permissions documents=model.documents
documents=model.documents categories=model.categories
onRefresh=(action 'onRefresh')}} categorySummary=model.categorySummary
categoryMembers=model.categoryMembers
rootDocCount=model.rootDocCount
onRefresh=(action 'onRefresh')}}
{{/layout/zone-content}} {{/layout/zone-content}}
{{/layout/zone-container}} {{/layout/zone-container}}

View file

@ -143,5 +143,13 @@ export default BaseService.extend({
}).then((response) => { }).then((response) => {
return response; return response;
}); });
},
getSpaceCategoryMembership(spaceId) {
return this.get('ajax').request(`category/member/space/${spaceId}`, {
method: 'GET'
}).then((response) => {
return response;
});
} }
}); });

View file

@ -51,24 +51,6 @@ export default Ember.Service.extend({
}); });
}, },
// getDocumentsByTag returns all documents for specified tag (not folder!).
getAllByTag(tag) {
return this.get('ajax').request(`documents?filter=tag&tag=${tag}`, {
method: "GET"
}).then((response) => {
let documents = Ember.ArrayProxy.create({
content: Ember.A([])
});
documents = response.map((doc) => {
let data = this.get('store').normalize('document', doc);
return this.get('store').push(data);
});
return documents;
});
},
// saveDocument updates an existing document record. // saveDocument updates an existing document record.
save(doc) { save(doc) {
let id = doc.get('id'); let id = doc.get('id');
@ -79,7 +61,7 @@ export default Ember.Service.extend({
}); });
}, },
changePageSequence: function (documentId, payload) { changePageSequence(documentId, payload) {
let url = `documents/${documentId}/pages/sequence`; let url = `documents/${documentId}/pages/sequence`;
return this.get('ajax').post(url, { return this.get('ajax').post(url, {
@ -97,7 +79,7 @@ export default Ember.Service.extend({
}); });
}, },
deleteDocument: function (documentId) { deleteDocument(documentId) {
let url = `documents/${documentId}`; let url = `documents/${documentId}`;
return this.get('ajax').request(url, { return this.get('ajax').request(url, {
@ -122,7 +104,7 @@ export default Ember.Service.extend({
}, },
// addPage inserts new page to an existing document. // addPage inserts new page to an existing document.
addPage: function (documentId, payload) { addPage(documentId, payload) {
let url = `documents/${documentId}/pages`; let url = `documents/${documentId}/pages`;
return this.get('ajax').post(url, { return this.get('ajax').post(url, {
@ -132,7 +114,7 @@ export default Ember.Service.extend({
}, },
// Nukes multiple pages from the document. // Nukes multiple pages from the document.
deletePages: function (documentId, pageId, payload) { deletePages(documentId, pageId, payload) {
let url = `documents/${documentId}/pages`; let url = `documents/${documentId}/pages`;
return this.get('ajax').request(url, { return this.get('ajax').request(url, {
@ -143,7 +125,7 @@ export default Ember.Service.extend({
}, },
// Nukes a single page from the document. // Nukes a single page from the document.
deletePage: function (documentId, pageId) { deletePage(documentId, pageId) {
let url = `documents/${documentId}/pages/${pageId}`; let url = `documents/${documentId}/pages/${pageId}`;
return this.get('ajax').request(url, { return this.get('ajax').request(url, {

View file

@ -38,14 +38,25 @@ $color-border: #f3f5f8;
$color-checkbox: #0092d3; $color-checkbox: #0092d3;
// lightblue sidebar
$color-sidebar: #f2faff; $color-sidebar: #f2faff;
$color-sidebar-border: #dff0f9; $color-sidebar-border: #dff0f9;
$color-sidebar-text: $color-off-black; $color-sidebar-text: $color-black;
$color-sidebar-link: $color-link; $color-sidebar-link: $color-link;
$color-selected-item: $color-sidebar;
$color-nav-button: $color-sidebar; $color-nav-button: $color-sidebar;
$color-nav-button-text: #2667af; $color-nav-button-text: #2667af;
$color-nav-button-border: #dff0f9; $color-nav-button-border: #dff0f9;
$color-selected-item: $color-sidebar;
// black sidebar
// $color-sidebar: $color-off-black;
// $color-sidebar-border: $color-black;
// $color-sidebar-text: $color-white;
// $color-sidebar-link: orange;
// $color-nav-button: orange;
// $color-nav-button-text: $color-off-black;
// $color-nav-button-border: $color-black;
// $color-selected-item: orange;
// Color utility classes for direct usage in HTML // Color utility classes for direct usage in HTML
.color-white { .color-white {

View file

@ -5,7 +5,7 @@
text-transform: uppercase; text-transform: uppercase;
color: $color-gray; color: $color-gray;
font-weight: bold; font-weight: bold;
font-size: 1.2rem; font-size: 1.0rem;
margin: 0 0 10px 0; margin: 0 0 10px 0;
} }
} }
@ -22,7 +22,7 @@
text-transform: uppercase; text-transform: uppercase;
color: $color-gray; color: $color-gray;
font-weight: bold; font-weight: bold;
font-size: 1.2rem; font-size: 1.0rem;
margin: 0 0 10px 0; margin: 0 0 10px 0;
} }
} }
@ -34,7 +34,7 @@
text-transform: uppercase; text-transform: uppercase;
color: $color-gray; color: $color-gray;
font-weight: bold; font-weight: bold;
font-size: 1.2rem; font-size: 1.0rem;
margin: 0 0 10px 0; margin: 0 0 10px 0;
} }

View file

@ -1,21 +1,28 @@
.folder-heading { .space-heading {
margin: 0 0 55px 0; margin: 0 0 55px 0;
.folder-title { .space-name {
font-size: 2rem; font-size: 2rem;
margin: 0 0 10px 0; margin: 0 0 10px 0;
font-weight: normal; font-weight: normal;
// color: $color-primary;
}
.space-summary {
font-size: 1.2rem;
color: $color-gray;
margin: 0 0 45px;
} }
} }
.edit-folder-heading { .edit-space-heading {
margin: 0 0 10px 0; margin: 0 0 10px 0;
.edit-folder-title { .edit-space-name {
> input { > input {
font-size: 2rem; font-size: 2rem;
font-weight: normal; font-weight: normal;
margin: 0 0 10px; margin: 0 0 40px 0;
color: $color-wysiwyg; color: $color-wysiwyg;
} }
} }
@ -23,6 +30,7 @@
.documents-list { .documents-list {
@include content-container(); @include content-container();
margin-bottom: 50px;
.document-item { .document-item {
margin: 0; margin: 0;
@ -68,7 +76,15 @@
&:hover { &:hover {
text-decoration: none; text-decoration: none;
color: $color-off-black; color: $color-link;
> .title {
color: $color-link;
}
> .snippet {
color: $color-link;
}
} }
> .title { > .title {
@ -116,3 +132,11 @@
color: $color-link; color: $color-link;
} }
} }
.category-filter {
margin-bottom: 30px;
> .selected {
@extend .button-nav;
}
}

View file

@ -21,11 +21,11 @@
> .link { > .link {
font-size: 1rem; font-size: 1rem;
color: $color-off-black; color: $color-sidebar-text;
text-decoration: none; text-decoration: none;
&:hover { &:hover {
color: $color-link; color: $color-sidebar-link;
} }
> .item { > .item {
@ -37,7 +37,7 @@
} }
> .selected { > .selected {
color: $color-link; color: $color-sidebar-link;
font-weight: bold; font-weight: bold;
} }
} }

View file

@ -15,7 +15,9 @@
<div class="regular-button button-blue">{{cat.category}}</div> <div class="regular-button button-blue">{{cat.category}}</div>
{{else}} {{else}}
{{#if canAddCategory}} {{#if canAddCategory}}
{{#link-to 'folder.settings.category' folder.id folder.slug}}Manage{{/link-to}} {{#unless canSelectCategory}}
{{#link-to 'folder.settings.category' folder.id folder.slug}}Manage{{/link-to}}
{{/unless}}
{{else}} {{else}}
<p>&nbsp;</p> <p>&nbsp;</p>
{{/if}} {{/if}}

View file

@ -1,22 +1,24 @@
<div class="documents-list"> {{#if (gt documents.length 0)}}
{{#each documents key="id" as |document|}} <div class="documents-list">
<div id="document-{{document.id}}"> {{#each documents key="id" as |document|}}
<div class="document-item {{if document.selected "selected-card"}}"> <div id="document-{{document.id}}">
{{#link-to 'document.index' folder.id folder.slug document.id document.slug class="link"}} <div class="document-item {{if document.selected "selected-card"}}">
<div class="title">{{ document.name }}</div> {{#link-to 'document.index' folder.id folder.slug document.id document.slug class="link"}}
<div class="snippet">{{ document.excerpt }}</div> <div class="title">{{ document.name }}</div>
<div class="chips">{{folder/document-tags documentTags=document.tags}}</div> <div class="snippet">{{ document.excerpt }}</div>
{{/link-to}} <div class="chips">{{folder/document-tags documentTags=document.tags}}</div>
{{#if session.authenticated}} {{/link-to}}
<div class="checkbox" {{action 'selectDocument' document.id}}> {{#if session.authenticated}}
{{#if document.selected}} <div class="checkbox" {{action 'selectDocument' document.id}}>
<i class="material-icons">check_box</i> {{#if document.selected}}
{{else}} <i class="material-icons">check_box</i>
<i class="material-icons">check_box_outline_blank</i> {{else}}
{{/if}} <i class="material-icons">check_box_outline_blank</i>
</div> {{/if}}
{{/if}} </div>
{{/if}}
</div>
</div> </div>
</div> {{/each}}
{{/each}} </div>
</div> {{/if}}

View file

@ -1,12 +1,15 @@
<div class="pull-left width-80"> <div class="pull-left width-80">
{{#unless editMode}} {{#unless editMode}}
<div class="folder-heading {{if permissions.spaceOwner 'cursor-pointer'}}" onclick={{if permissions.spaceOwner (action 'toggleEdit')}}> <div class="space-heading {{if permissions.spaceOwner 'cursor-pointer'}}" onclick={{if permissions.spaceOwner (action 'toggleEdit')}}>
<h1 class="folder-title">{{folder.name}}</h1> <h1 class="space-name">{{folder.name}}</h1>
<div class="space-summary">
This space contains {{documents.length}} {{if (eq rootDocCount.length 1) 'document' 'documents'}} across {{categories.length}} {{if (eq categories.length 1) 'category' 'categories'}}
</div>
</div> </div>
{{else}} {{else}}
<form {{action "onSave" on="submit"}}> <form {{action "onSave" on="submit"}}>
<div class="edit-folder-heading"> <div class="edit-space-heading">
<div class="input-inline input-transparent edit-folder-title"> <div class="input-inline input-transparent edit-space-name">
{{focus-input id="folder-name" type="text" value=folderName class=(if hasNameError 'error-inline') placeholder="Name" autocomplete="off"}} {{focus-input id="folder-name" type="text" value=folderName class=(if hasNameError 'error-inline') placeholder="Name" autocomplete="off"}}
</div> </div>
<div> <div>

View file

@ -1,17 +1,36 @@
{{folder/space-heading folder=folder permissions=permissions}} <div class="margin-bottom-20">
{{folder/space-heading folder=folder permissions=permissions documents=documents categories=categories}}
{{folder/space-toolbar folders=folders folder=folder {{folder/space-toolbar folders=folders folder=folder
permissions=permissions hasSelectedDocuments=hasSelectedDocuments permissions=permissions hasSelectedDocuments=hasSelectedDocuments
onDeleteDocument=(action 'onDeleteDocument') onMoveDocument=(action 'onMoveDocument') onDeleteDocument=(action 'onDeleteDocument') onMoveDocument=(action 'onMoveDocument')
onDeleteSpace=(action 'onDeleteSpace') onStartDocument=(action 'onStartDocument')}} onDeleteSpace=(action 'onDeleteSpace') onStartDocument=(action 'onStartDocument')}}
</div>
<div class="margin-bottom-20 clearfix" /> <div class="clearfix" />
{{#if showStartDocument}} {{#if showStartDocument}}
{{folder/start-document folder=folder templates=templates permissions=permissions {{folder/start-document folder=folder templates=templates permissions=permissions
onImport=(action 'onImport') onHideStartDocument=(action 'onHideStartDocument')}} onImport=(action 'onImport') onHideStartDocument=(action 'onHideStartDocument')}}
{{else}} {{else}}
{{folder/documents-list documents=documents folders=folders folder=folder
{{#if (gt categories.length 0)}}
<div class="category-filter">
{{#if (gt rootDocCount 0)}}
<div class="regular-button button-white {{if (eq folder.id selectedCategory) 'selected'}}" id="uncategorized-button" data-tooltip="Documents without category" data-tooltip-position="top center" {{action 'onDocumentFilter' 'space' folder.id}}>
{{folder.name}} ({{rootDocCount}})
</div>
{{/if}}
{{#each categories as |cat index|}}
<div class="button-gap"/>
<div class="regular-button button-white {{if (eq cat.id selectedCategory) 'selected'}}" {{action 'onDocumentFilter' 'category' cat.id}}>
{{cat.category}} ({{cat.docCount}})
</div>
{{/each}}
</div>
{{/if}}
{{folder/documents-list documents=filteredDocs folders=folders folder=folder
templates=templates permissions=permissions selectedDocuments=(mut selectedDocuments) templates=templates permissions=permissions selectedDocuments=(mut selectedDocuments)
onImport=(action 'onImport')}} onImport=(action 'onImport')}}
{{/if}} {{/if}}

View file

@ -85,7 +85,6 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "import/folder/{folderID}", []string{"POST", "OPTIONS"}, nil, conversion.UploadConvert) Add(rt, RoutePrefixPrivate, "import/folder/{folderID}", []string{"POST", "OPTIONS"}, nil, conversion.UploadConvert)
Add(rt, RoutePrefixPrivate, "documents", []string{"GET", "OPTIONS"}, []string{"filter", "tag"}, document.ByTag)
Add(rt, RoutePrefixPrivate, "documents", []string{"GET", "OPTIONS"}, nil, document.BySpace) Add(rt, RoutePrefixPrivate, "documents", []string{"GET", "OPTIONS"}, nil, document.BySpace)
Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, document.Get) Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, document.Get)
Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, document.Update) Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, document.Update)
@ -136,6 +135,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{"PUT", "OPTIONS"}, nil, permission.SetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryPermissions) 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/{categoryID}/user", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryViewers)
Add(rt, RoutePrefixPrivate, "category/member/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.GetSpaceCategoryMembers)
Add(rt, RoutePrefixPrivate, "category/member", []string{"POST", "OPTIONS"}, nil, category.SetDocumentCategoryMembership) 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/{userID}/password", []string{"POST", "OPTIONS"}, nil, user.ChangePassword)