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:
parent
2cee83d570
commit
5481de4e1c
21 changed files with 342 additions and 157 deletions
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}}
|
|
@ -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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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, {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> </p>
|
<p> </p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -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}}
|
|
@ -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>
|
||||||
|
|
|
@ -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}}
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue