mirror of
https://github.com/documize/community.git
synced 2025-07-21 14:19:43 +02:00
category permission admin, re-vamped view layout
This commit is contained in:
parent
0c152c219f
commit
3f31d6d15e
48 changed files with 753 additions and 373 deletions
|
@ -279,7 +279,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
/*
|
||||
6. add category view permission !!!
|
||||
7. link/unlink document to category
|
||||
8. filter space documents by category -- URL param? nested route?
|
||||
- category view permission handling
|
||||
- filter users using new permission
|
||||
- link/unlink document to category
|
||||
- check print/pdf
|
||||
- filter space documents by category -- URL param? nested route?
|
||||
*/
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/documize/community/model/audit"
|
||||
"github.com/documize/community/model/permission"
|
||||
"github.com/documize/community/model/space"
|
||||
"github.com/documize/community/model/user"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
|
@ -43,25 +44,20 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
|||
method := "space.SetPermissions"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !ctx.Editor {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
id := request.Param(r, "spaceID")
|
||||
if len(id) == 0 {
|
||||
response.WriteMissingDataError(w, method, "spaceID")
|
||||
return
|
||||
}
|
||||
|
||||
sp, err := h.Store.Space.Get(ctx, id)
|
||||
if err != nil {
|
||||
response.WriteNotFoundError(w, method, "space not found")
|
||||
if !HasPermission(ctx, *h.Store, id, permission.SpaceManage, permission.SpaceOwner) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
if sp.UserID != ctx.UserID {
|
||||
response.WriteForbiddenError(w)
|
||||
sp, err := h.Store.Space.Get(ctx, id)
|
||||
if err != nil {
|
||||
response.WriteNotFoundError(w, method, "space not found")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -220,7 +216,7 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
|||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// GetSpacePermissions returns permissions for alll users for given space.
|
||||
// GetSpacePermissions returns permissions for all users for given space.
|
||||
func (h *Handler) GetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "space.GetPermissions"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
@ -276,3 +272,105 @@ func (h *Handler) GetUserSpacePermissions(w http.ResponseWriter, r *http.Request
|
|||
record := permission.DecodeUserPermissions(perms)
|
||||
response.WriteJSON(w, record)
|
||||
}
|
||||
|
||||
// GetCategoryPermissions returns user permissions for given category.
|
||||
func (h *Handler) GetCategoryPermissions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "space.GetCategoryPermissions"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
categoryID := request.Param(r, "categoryID")
|
||||
if len(categoryID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "categoryID")
|
||||
return
|
||||
}
|
||||
|
||||
u, err := h.Store.Permission.GetCategoryUsers(ctx, categoryID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(u) == 0 {
|
||||
u = []user.User{}
|
||||
}
|
||||
|
||||
response.WriteJSON(w, u)
|
||||
}
|
||||
|
||||
// SetCategoryPermissions persists specified category permissions
|
||||
func (h *Handler) SetCategoryPermissions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "permission.SetCategoryPermissions"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
id := request.Param(r, "categoryID")
|
||||
if len(id) == 0 {
|
||||
response.WriteMissingDataError(w, method, "categoryID")
|
||||
return
|
||||
}
|
||||
|
||||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
var model = []permission.CategoryViewRequestModel{}
|
||||
err = json.Unmarshal(body, &model)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(model) == 0 {
|
||||
response.WriteEmpty(w)
|
||||
return
|
||||
}
|
||||
|
||||
spaceID := model[0].SpaceID
|
||||
if !HasPermission(ctx, *h.Store, spaceID, permission.SpaceManage, permission.SpaceOwner) {
|
||||
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
|
||||
}
|
||||
|
||||
// Nuke all previous permissions for this category
|
||||
_, err = h.Store.Permission.DeleteCategoryPermissions(ctx, id)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, m := range model {
|
||||
perm := permission.Permission{}
|
||||
perm.OrgID = ctx.OrgID
|
||||
perm.Who = "user"
|
||||
perm.WhoID = m.UserID
|
||||
perm.Scope = "object"
|
||||
perm.Location = "category"
|
||||
perm.RefID = m.CategoryID
|
||||
perm.Action = permission.CategoryView
|
||||
|
||||
err = h.Store.Permission.AddPermission(ctx, perm)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeCategoryPermission)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/store/mysql"
|
||||
"github.com/documize/community/model/permission"
|
||||
"github.com/documize/community/model/user"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -155,3 +156,50 @@ func (s Scope) DeleteSpaceCategoryPermissions(ctx domain.RequestContext, spaceID
|
|||
|
||||
return b.DeleteWhere(ctx.Transaction, sql)
|
||||
}
|
||||
|
||||
// GetCategoryPermissions returns category permissions for all users.
|
||||
func (s Scope) GetCategoryPermissions(ctx domain.RequestContext, catID string) (r []permission.Permission, err error) {
|
||||
err = s.Runtime.Db.Select(&r, `
|
||||
SELECT id, orgid, who, whoid, action, scope, location, refid
|
||||
FROM permission WHERE orgid=? AND location='category' AND refid=? AND who='user'
|
||||
UNION ALL
|
||||
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
|
||||
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='space' AND p.refid=?
|
||||
AND p.who='role'`,
|
||||
ctx.OrgID, catID, ctx.OrgID, catID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select category permissions %s", catID))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetCategoryUsers returns space permissions for all users.
|
||||
func (s Scope) GetCategoryUsers(ctx domain.RequestContext, catID string) (u []user.User, err error) {
|
||||
err = s.Runtime.Db.Select(&u, `
|
||||
SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised
|
||||
FROM user u, account a
|
||||
WHERE a.orgid=? AND u.refid = a.userid AND a.active=1 AND u.refid IN (
|
||||
SELECT whoid from permission WHERE orgid=? AND who='user' AND location='category' AND refid=? UNION ALL
|
||||
SELECT r.userid from rolemember r LEFT JOIN permission p ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
|
||||
AND p.location='category' AND p.refid=?
|
||||
)
|
||||
GROUP by u.id
|
||||
ORDER BY firstname, lastname`,
|
||||
ctx.OrgID, ctx.OrgID, catID, ctx.OrgID, catID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select category user %s", catID))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -86,6 +86,8 @@ type PermissionStorer interface {
|
|||
DeleteUserPermissions(ctx RequestContext, userID string) (rows int64, err error)
|
||||
DeleteCategoryPermissions(ctx RequestContext, categoryID string) (rows int64, err error)
|
||||
DeleteSpaceCategoryPermissions(ctx RequestContext, spaceID string) (rows int64, err error)
|
||||
GetCategoryPermissions(ctx RequestContext, catID string) (r []permission.Permission, err error)
|
||||
GetCategoryUsers(ctx RequestContext, catID string) (u []user.User, err error)
|
||||
}
|
||||
|
||||
// UserStorer defines required methods for user management
|
||||
|
|
|
@ -17,7 +17,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
documentService: Ember.inject.service('document'),
|
||||
appMeta: Ember.inject.service(),
|
||||
drop: null,
|
||||
emptyState: Ember.computed.empty('files'),
|
||||
hasAttachments: Ember.computed.notEmpty('files'),
|
||||
deleteAttachment: {
|
||||
id: "",
|
||||
name: "",
|
||||
|
@ -104,7 +104,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
target: $(".delete-attachment-" + id)[0],
|
||||
content: $(".delete-attachment-dialog")[0],
|
||||
classes: 'drop-theme-basic',
|
||||
position: "bottom left",
|
||||
position: "bottom right",
|
||||
openOn: "always",
|
||||
tetherOptions: {
|
||||
offset: "5px 0",
|
|
@ -31,14 +31,9 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
|
|||
name: "",
|
||||
description: ""
|
||||
},
|
||||
tab: '',
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (is.empty(this.get('tab')) || is.undefined(this.get('tab'))) {
|
||||
this.set('tab', 'index');
|
||||
}
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
|
@ -49,20 +44,18 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
|
|||
|
||||
this.set('pinState.pinId', this.get('pinned').isDocumentPinned(this.get('document.id')));
|
||||
this.set('pinState.isPinned', this.get('pinState.pinId') !== '');
|
||||
this.set('pinState.newName', this.get('document.name').substring(0,3).toUpperCase());
|
||||
this.set('pinState.newName', this.get('document.name'));
|
||||
},
|
||||
|
||||
didRender() {
|
||||
this.destroyTooltips();
|
||||
|
||||
if (this.get('permissions.documentEdit')) {
|
||||
this.addTooltip(document.getElementById("document-activity-button"));
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
onChangeTab(tab) {
|
||||
this.set('tab', tab);
|
||||
},
|
||||
|
||||
onTagChange(tags) {
|
||||
let doc = this.get('document');
|
||||
doc.set('tags', tags);
|
||||
this.get('documentService').save(doc);
|
||||
},
|
||||
|
||||
onMenuOpen() {
|
||||
this.set('menuOpen', !this.get('menuOpen'));
|
||||
},
|
||||
|
@ -140,7 +133,10 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
|
|||
onLayoutChange(layout) {
|
||||
let doc = this.get('document');
|
||||
doc.set('layout', layout);
|
||||
|
||||
if (this.get('permissions.documentEdit')) {
|
||||
this.get('documentService').save(doc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -76,7 +76,7 @@ export default Ember.Component.extend(TooltipMixin, {
|
|||
let permissions = this.get('permissions');
|
||||
|
||||
return permissions.get('documentDelete') || permissions.get('documentCopy') ||
|
||||
permissions.get('documentMove') || permissions.get('documentTemplate');;
|
||||
permissions.get('documentMove') || permissions.get('documentTemplate');
|
||||
}),
|
||||
|
||||
didRender() {
|
||||
|
|
34
gui/app/components/document/space-category.js
Normal file
34
gui/app/components/document/space-category.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
// 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';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
import NotifierMixin from '../../mixins/notifier';
|
||||
|
||||
export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
|
||||
documentService: Ember.inject.service('document'),
|
||||
sectionService: Ember.inject.service('section'),
|
||||
sessionService: Ember.inject.service('session'),
|
||||
appMeta: Ember.inject.service(),
|
||||
userService: Ember.inject.service('user'),
|
||||
localStorage: Ember.inject.service(),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
actions: {
|
||||
}
|
||||
});
|
|
@ -11,25 +11,59 @@
|
|||
|
||||
import Ember from 'ember';
|
||||
import NotifierMixin from '../../mixins/notifier';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
|
||||
const {
|
||||
inject: { service }
|
||||
} = Ember;
|
||||
|
||||
export default Ember.Component.extend(NotifierMixin, {
|
||||
folderService: service('folder'),
|
||||
export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||
userService: service('user'),
|
||||
categoryService: service('category'),
|
||||
appMeta: service(),
|
||||
store: service(),
|
||||
newCategory: '',
|
||||
drop: null,
|
||||
users: [],
|
||||
|
||||
didReceiveAttrs() {
|
||||
this.load();
|
||||
},
|
||||
|
||||
didRender() {
|
||||
// this.addTooltip(this.$(".action"));
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
let drop = this.get('drop');
|
||||
|
||||
if (is.not.null(drop)) {
|
||||
drop.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
load() {
|
||||
// get categories
|
||||
this.get('categoryService').getAll(this.get('folder.id')).then((c) => {
|
||||
this.set('category', c);
|
||||
|
||||
// get users that this space admin user can see
|
||||
this.get('userService').getAll().then((users) => {
|
||||
// set up Everyone user
|
||||
let u = {
|
||||
orgId: this.get('folder.orgId'),
|
||||
folderId: this.get('folder.id'),
|
||||
userId: '',
|
||||
firstname: 'Everyone',
|
||||
lastname: '',
|
||||
};
|
||||
|
||||
let data = this.get('store').normalize('user', u)
|
||||
users.pushObject(this.get('store').push(data));
|
||||
|
||||
users = users.sortBy('firstname', 'lastname');
|
||||
this.set('users', users);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -76,7 +110,7 @@ export default Ember.Component.extend(NotifierMixin, {
|
|||
this.setEdit(id, true);
|
||||
},
|
||||
|
||||
onCancel(id) {
|
||||
onEditCancel(id) {
|
||||
this.setEdit(id, false);
|
||||
this.load();
|
||||
},
|
||||
|
@ -94,6 +128,69 @@ export default Ember.Component.extend(NotifierMixin, {
|
|||
this.get('categoryService').save(cat).then(() => {
|
||||
this.load();
|
||||
});
|
||||
},
|
||||
|
||||
onShowAccessPicker(catId) {
|
||||
let users = this.get('users');
|
||||
let category = this.get('category').findBy('id', catId);
|
||||
|
||||
this.get('categoryService').getViewers(category.get('id')).then((viewers) => {
|
||||
// mark those users as selected that have already been given permission
|
||||
// to see the current category;
|
||||
console.log(viewers);
|
||||
|
||||
users.forEach((user) => {
|
||||
let selected = viewers.isAny('id', user.get('id'));
|
||||
user.set('selected', selected);
|
||||
});
|
||||
|
||||
this.set('categoryUsers', users);
|
||||
this.set('currentCategory', category);
|
||||
|
||||
$(".category-access-dialog").css("display", "block");
|
||||
|
||||
let drop = new Drop({
|
||||
target: $("#category-access-button-" + catId)[0],
|
||||
content: $(".category-access-dialog")[0],
|
||||
classes: 'drop-theme-basic',
|
||||
position: "bottom right",
|
||||
openOn: "always",
|
||||
tetherOptions: {
|
||||
offset: "5px 0",
|
||||
targetOffset: "10px 0"
|
||||
},
|
||||
remove: false
|
||||
});
|
||||
|
||||
this.set('drop', drop);
|
||||
});
|
||||
},
|
||||
|
||||
onGrantCancel() {
|
||||
let drop = this.get('drop');
|
||||
drop.close();
|
||||
},
|
||||
|
||||
onGrantAccess() {
|
||||
let category = this.get('currentCategory');
|
||||
let users = this.get('categoryUsers').filterBy('selected', true);
|
||||
let viewers = [];
|
||||
|
||||
users.forEach((user) => {
|
||||
let v = {
|
||||
orgId: this.get('folder.orgId'),
|
||||
folderId: this.get('folder.id'),
|
||||
categoryId: category.get('id'),
|
||||
userId: user.get('id')
|
||||
};
|
||||
|
||||
viewers.push(v);
|
||||
});
|
||||
|
||||
this.get('categoryService').setViewers(category.get('id'), viewers).then( () => {});
|
||||
|
||||
let drop = this.get('drop');
|
||||
drop.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -60,6 +60,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
|
|||
if (this.get('permissions.documentMove')) {
|
||||
this.addTooltip(document.getElementById("move-documents-button"));
|
||||
}
|
||||
|
||||
if (this.get('permissions.documentDelete')) {
|
||||
this.addTooltip(document.getElementById("delete-documents-button"));
|
||||
}
|
||||
|
@ -67,10 +68,12 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
|
|||
if (this.get('permissions.spaceOwner')) {
|
||||
this.addTooltip(document.getElementById("space-delete-button"));
|
||||
}
|
||||
|
||||
if (this.get('permissions.spaceManage')) {
|
||||
this.addTooltip(document.getElementById("space-settings-button"));
|
||||
}
|
||||
if (this.get('session.authenticated')) {
|
||||
|
||||
if (this.get('pinState.isPinned')) {
|
||||
this.addTooltip(document.getElementById("space-unpin-button"));
|
||||
} else {
|
||||
this.addTooltip(document.getElementById("space-pin-button"));
|
||||
|
|
23
gui/app/components/ui/ui-list-picker.js
Normal file
23
gui/app/components/ui/ui-list-picker.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
// 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({
|
||||
nameField: 'category',
|
||||
items: [],
|
||||
|
||||
actions: {
|
||||
onToggle(item) {
|
||||
Ember.set(item, 'selected', !item.get('selected'));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -15,7 +15,6 @@ export default Ember.Mixin.create({
|
|||
tooltips: [],
|
||||
|
||||
addTooltip(elem) {
|
||||
|
||||
if (elem == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -226,6 +226,12 @@ export default Ember.Controller.extend(NotifierMixin, {
|
|||
if (this.get('pageId') !== id && id !== '') {
|
||||
this.set('pageId', id);
|
||||
}
|
||||
},
|
||||
|
||||
onTagChange(tags) {
|
||||
let doc = this.get('model.document');
|
||||
doc.set('tags', tags);
|
||||
this.get('documentService').save(doc);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,27 +1,44 @@
|
|||
{{#layout/zone-container}}
|
||||
|
||||
{{#layout/zone-sidebar}}
|
||||
{{document/sidebar-zone folders=model.folders folder=model.folder document=model.document
|
||||
pages=model.pages sections=model.section links=model.links permissions=model.permissions tab=tab
|
||||
onDocumentDelete=(action 'onDocumentDelete') onSaveTemplate=(action 'onSaveTemplate')
|
||||
{{document/document-index
|
||||
document=model.document folder=model.folder pages=model.pages page=model.page permissions=model.permissions
|
||||
onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange')
|
||||
onGotoPage=(action 'onGotoPage')}}
|
||||
{{/layout/zone-sidebar}}
|
||||
|
||||
{{#layout/zone-content}}
|
||||
<div id="zone-document-content" class="zone-document-content">
|
||||
<div class="back-to-space">
|
||||
{{#link-to 'folder' model.folder.id model.folder.slug}}
|
||||
<div class="regular-button button-gray">
|
||||
<i class="material-icons">arrow_back</i>
|
||||
<div class="name">{{model.folder.name}}</div>
|
||||
|
||||
<div class="pull-left">
|
||||
{{document/space-category document=model.document folder=model.folder folders=model.folders permissions=model.permissions}}
|
||||
</div>
|
||||
{{/link-to}}
|
||||
|
||||
<div class="pull-right">
|
||||
{{document/document-toolbar
|
||||
document=model.document folder=model.folder folders=model.folders permissions=model.permissions
|
||||
onDocumentDelete=(action 'onDocumentDelete') onSaveTemplate=(action 'onSaveTemplate')
|
||||
onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange')
|
||||
onGotoPage=(action 'onGotoPage')}}
|
||||
</div>
|
||||
<div class="clearfix"/>
|
||||
|
||||
{{#if model.document.template}}
|
||||
<div class="document-template-header">Template</div>
|
||||
{{/if}}
|
||||
|
||||
{{document/document-heading document=model.document permissions=model.permissions onSaveDocument=(action 'onSaveDocument')}}
|
||||
{{document/document-view document=model.document links=model.links pages=model.pages
|
||||
|
||||
{{document/tag-editor documentTags=model.document.tags permissions=model.permissions onChange=(action 'onTagChange')}}
|
||||
|
||||
{{document/document-view
|
||||
document=model.document links=model.links pages=model.pages
|
||||
folder=model.folder folders=model.folders sections=model.sections permissions=model.permissions pageId=pageId
|
||||
onSavePage=(action 'onSavePage') onInsertSection=(action 'onInsertSection')
|
||||
onSavePageAsBlock=(action 'onSavePageAsBlock') onDeleteBlock=(action 'onDeleteBlock') onGotoPage=(action 'onGotoPage')
|
||||
onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')}}
|
||||
|
||||
</div>
|
||||
{{/layout/zone-content}}
|
||||
|
||||
{{/layout/zone-container}}
|
||||
|
|
|
@ -15,7 +15,7 @@ import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-rout
|
|||
export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
||||
beforeModel: function (transition) {
|
||||
if (is.equal(transition.targetName, 'folder.settings.index')) {
|
||||
this.transitionTo('folder.settings.security');
|
||||
this.transitionTo('folder.settings.invitation');
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
{{#link-to 'folder.settings.invitation' activeClass='selected' class="option" tagName="li"}}Invite{{/link-to}}
|
||||
{{/if}}
|
||||
{{#link-to 'folder.settings.security' activeClass='selected' class="option" tagName="li"}}Secure{{/link-to}}
|
||||
{{#link-to 'folder.settings.category' activeClass='selected' class="option" tagName="li"}}Organize{{/link-to}}
|
||||
{{#link-to 'folder.settings.category' activeClass='selected' class="option" tagName="li"}}Categorize{{/link-to}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -83,5 +83,30 @@ export default BaseService.extend({
|
|||
return this.get('ajax').request(`category/${categoryId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Get list of users who can see given category
|
||||
getViewers(categoryId) {
|
||||
return this.get('ajax').request(`category/${categoryId}/permission`, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
let data = [];
|
||||
|
||||
data = response.map((obj) => {
|
||||
let data = this.get('store').normalize('user', obj);
|
||||
return this.get('store').push(data);
|
||||
});
|
||||
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
// Save list of users who can see given category
|
||||
setViewers(categoryId, viewers) {
|
||||
return this.get('ajax').request(`category/${categoryId}/permission`, {
|
||||
method: 'PUT',
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(viewers)
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -66,7 +66,6 @@ export default Ember.Service.extend({
|
|||
});
|
||||
},
|
||||
|
||||
|
||||
// Returns all users that can see folder.
|
||||
getFolderUsers(folderId) {
|
||||
let url = `users/folder/${folderId}`;
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
#sidebar-wrapper,
|
||||
.sidebar-wrapper,
|
||||
.sidebar-common,
|
||||
.sidebar-toolbar,
|
||||
.sidebar-panel,
|
||||
.edit-document-heading,
|
||||
.back-to-space,
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
}
|
||||
|
||||
.back-to-space {
|
||||
margin: 10px 0;
|
||||
margin: 0 0 10px 0;
|
||||
display: inline-block;
|
||||
|
||||
> a {
|
||||
> .regular-button {
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
@import "history.scss";
|
||||
@import "inline-editor.scss";
|
||||
@import "section-editor.scss";
|
||||
@import "sidebar-view-activity.scss";
|
||||
@import "sidebar-view-attachments.scss";
|
||||
@import "sidebar-view-index.scss";
|
||||
@import "activity.scss";
|
||||
@import "attachments.scss";
|
||||
@import "toc.scss";
|
||||
@import "view.scss";
|
||||
@import "wysiwyg.scss";
|
||||
|
|
|
@ -1,31 +1,17 @@
|
|||
.document-sidebar-view-attachments {
|
||||
.document-attachments {
|
||||
margin: 0;
|
||||
|
||||
> .upload-document-files {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: $color-gray;
|
||||
border: 1px solid $color-stroke;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.7rem;
|
||||
margin: 10px 0 0 0;
|
||||
@include ease-in();
|
||||
|
||||
&:hover {
|
||||
border-color: $color-link;
|
||||
color: $color-link;
|
||||
}
|
||||
|
||||
> .dz-preview,
|
||||
.dz-processing {
|
||||
> .dz-preview, .dz-processing {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
> .list {
|
||||
margin: 0 0 50px;
|
||||
margin: 20px 0 0 0;
|
||||
padding: 7px 0;
|
||||
|
||||
> .item {
|
||||
|
@ -45,7 +31,7 @@
|
|||
|
||||
> a {
|
||||
@extend .truncate;
|
||||
width: 200px;
|
||||
width: 80%;
|
||||
color: $color-gray;
|
||||
|
||||
&:hover {
|
||||
|
@ -56,7 +42,8 @@
|
|||
@extend .truncate;
|
||||
display: inline-block;
|
||||
font-size: 0.9rem;
|
||||
width: 200px;
|
||||
width: 80%;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
.doc-excerpt {
|
||||
font-size: 1rem;
|
||||
color: $color-gray;
|
||||
margin: 0 0 45px;
|
||||
margin: 0 0 60px;
|
||||
}
|
||||
|
||||
.edit-document-heading {
|
||||
|
@ -341,3 +341,25 @@
|
|||
.dropdown-page-toolbar {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.document-toolbar {
|
||||
> .round-button-mono {
|
||||
background-color: $color-white;
|
||||
border: 1px solid $color-gray;
|
||||
|
||||
> .material-icons {
|
||||
@include ease-in();
|
||||
color: $color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.document-template-header {
|
||||
color: $color-goldy;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.document-tags {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
|
|
@ -43,15 +43,26 @@
|
|||
|
||||
> .row {
|
||||
margin: 15px 0;
|
||||
padding: 8px 10px;
|
||||
padding: 15px;
|
||||
background-color: $color-off-white;
|
||||
@include border-radius(2px);
|
||||
|
||||
> .category {
|
||||
font-size: 1.2rem;
|
||||
vertical-align: bottom;
|
||||
display: inline-block;
|
||||
|
||||
> .name {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
> .info {
|
||||
font-size: 0.9rem;
|
||||
margin-top: 8px;
|
||||
color: $color-gray;
|
||||
}
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
> .action {
|
||||
|
@ -63,7 +74,7 @@
|
|||
display: inline-block;
|
||||
|
||||
> input {
|
||||
margin: 0;
|
||||
margin: 0 0 8px 0;
|
||||
padding: 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
@ -72,3 +83,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.category-access-dialog {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -58,48 +58,6 @@ $sidebar-width: 400px;
|
|||
#sidebar-wrapper {
|
||||
width: $sidebar-width;
|
||||
}
|
||||
|
||||
// #page-content-wrapper {
|
||||
// padding: 30px;
|
||||
// position: relative;
|
||||
// }
|
||||
}
|
||||
|
||||
.sidebar-toolbar {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
background-color: $color-primary;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
padding: 40px 0 0 0;
|
||||
|
||||
> .selected {
|
||||
background-color: $color-link !important;
|
||||
border: 1px solid $color-link !important;
|
||||
|
||||
> .material-icons {
|
||||
color: $color-white !important;
|
||||
}
|
||||
}
|
||||
|
||||
> .round-button-mono {
|
||||
background-color: $color-off-white;
|
||||
border: 1px solid $color-off-white;
|
||||
|
||||
> .material-icons {
|
||||
@include ease-in();
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> .material-icons {
|
||||
color: $color-link;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-common {
|
||||
|
@ -108,20 +66,6 @@ $sidebar-width: 400px;
|
|||
padding: 40px 20px 0px 20px;
|
||||
margin-left: 20px;
|
||||
|
||||
> .pinner {
|
||||
cursor: pointer;
|
||||
|
||||
> .material-icons {
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
> .template-header {
|
||||
color: $color-goldy;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.zone-sidebar-page-title {
|
||||
color: $color-primary;
|
||||
font-size: 1.3rem;
|
||||
|
@ -150,7 +94,7 @@ $sidebar-width: 400px;
|
|||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.folder-sidebar-form-wrapper, .document-sidebar-form-wrapper {
|
||||
.document-sidebar-form-wrapper {
|
||||
padding: 20px;
|
||||
border: 1px solid $color-stroke;
|
||||
@include border-radius(3px);
|
||||
|
|
41
gui/app/styles/widget/widget-list-picker.scss
Normal file
41
gui/app/styles/widget/widget-list-picker.scss
Normal file
|
@ -0,0 +1,41 @@
|
|||
.widget-list-picker {
|
||||
margin: 10px 0;
|
||||
|
||||
> .options {
|
||||
width: 300px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
|
||||
> .option {
|
||||
margin: 0 0 5px 0;
|
||||
padding: 10px 15px;
|
||||
color: $color-gray;
|
||||
background-color: $color-off-white;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: $color-white;
|
||||
background-color: $color-gray;
|
||||
}
|
||||
|
||||
> .text {
|
||||
width: 220px;
|
||||
overflow: hidden;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> .material-icons {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
> .selected {
|
||||
color: $color-white !important;
|
||||
background-color: $color-link !important;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,15 +64,16 @@
|
|||
@import "widget-avatar";
|
||||
@import "widget-button";
|
||||
@import "widget-card";
|
||||
@import "widget-checkbox";
|
||||
@import "widget-chip";
|
||||
@import "widget-dropdown";
|
||||
@import "widget-input";
|
||||
@import "widget-list-picker";
|
||||
@import "widget-notification";
|
||||
@import "widget-radio";
|
||||
@import "widget-selection";
|
||||
@import "widget-sidebar-menu";
|
||||
@import "widget-symbol";
|
||||
@import "widget-tab";
|
||||
@import "widget-table";
|
||||
@import "widget-tooltip";
|
||||
@import "widget-checkbox";
|
||||
@import "widget-radio";
|
||||
@import "widget-tab";
|
||||
@import "widget-selection";
|
||||
@import "widget-symbol";
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<div class="document-attachments">
|
||||
{{#if hasAttachments}}
|
||||
<ul class="list">
|
||||
{{#each files key="id" as |a index|}}
|
||||
<li class="item">
|
||||
<img class="icon" src="/assets/img/attachments/{{document/file-icon a.extension}}" />
|
||||
<a href="{{ appMeta.endpoint }}/public/attachments/{{ appMeta.orgId }}/{{ a.id }}">
|
||||
<span class="file">{{ a.filename }}</span>
|
||||
</a>
|
||||
{{#if permissions.documentEdit}}
|
||||
<div class="action round-button-mono">
|
||||
<i class="material-icons color-gray delete-attachment-{{a.id}}" title="Delete" {{action 'onConfirmDelete' a.id a.filename}}>delete</i>
|
||||
</div>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
{{#if permissions.documentEdit}}
|
||||
<div class="upload-document-files">
|
||||
<div class="chip chip-action">
|
||||
<span id="upload-document-files" class="chip-text">+ attachment</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="dropdown-dialog delete-attachment-dialog">
|
||||
<div class="content">
|
||||
<p>Are you sure you want to delete <span class="bold">{{deleteAttachment.name}}?</span></p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="flat-button" {{action 'onCancel'}}>
|
||||
cancel
|
||||
</div>
|
||||
<div class="flat-button flat-red" {{action 'onDelete'}}>
|
||||
delete
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
35
gui/app/templates/components/document/document-index.hbs
Normal file
35
gui/app/templates/components/document/document-index.hbs
Normal file
|
@ -0,0 +1,35 @@
|
|||
<div class="sidebar-wrapper">
|
||||
<div class="sidebar-panel">
|
||||
<div class="document-sidebar-view-index">
|
||||
<div class="structure">
|
||||
{{#if session.authenticated}}
|
||||
{{#unless emptyState}}
|
||||
<div id="tocToolbar" class="hidden-xs hidden-sm toc-controls {{if state.actionablePage 'current-page' ''}}">
|
||||
<div id="toc-up-button" class="round-button-mono {{if state.upDisabled 'disabled'}}" data-tooltip="Move up" data-tooltip-position="top center" {{action 'pageUp'}}>
|
||||
<i class="material-icons">arrow_upward</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-down-button" class="round-button-mono {{if state.downDisabled 'disabled'}}" data-tooltip="Move down" data-tooltip-position="top center" {{action 'pageDown'}}>
|
||||
<i class="material-icons">arrow_downward</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-outdent-button" class="round-button-mono {{if state.outdentDisabled 'disabled'}}" data-tooltip="Outdent" data-tooltip-position="top center" {{action 'pageOutdent'}}>
|
||||
<i class="material-icons">format_indent_decrease</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-indent-button" class="round-button-mono {{if state.indentDisabled 'disabled'}}" data-tooltip="Indent" data-tooltip-position="top center" {{action 'pageIndent'}}>
|
||||
<i class="material-icons">format_indent_increase</i>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
<ul class="index-list">
|
||||
{{#each pages key="id" as |p index|}}
|
||||
{{document/index-entry page=p index=index onClick=(action 'onEntryClick')}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
80
gui/app/templates/components/document/document-toolbar.hbs
Normal file
80
gui/app/templates/components/document/document-toolbar.hbs
Normal file
|
@ -0,0 +1,80 @@
|
|||
|
||||
{{#if (is-equal tab 'activitysdsd')}}
|
||||
{{document/sidebar-view-activity document=document pages=pages permissions=permissions}}
|
||||
{{/if}}
|
||||
|
||||
|
||||
|
||||
<div class="document-toolbar">
|
||||
<div class="round-button-mono" id="document-more-button">
|
||||
<i class="material-icons">more_vert</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#dropdown-menu target="document-more-button" position="bottom right" open="click" onOpenCallback=(action 'onMenuOpen') onCloseCallback=(action 'onMenuOpen')}}
|
||||
<ul class="menu">
|
||||
{{#if session.authenticated}}
|
||||
{{#if (is-equal document.layout 'section')}}
|
||||
<li class="item" {{action 'onLayoutChange' 'doc'}}>Flat view</li>
|
||||
{{else}}
|
||||
<li class="item" {{action 'onLayoutChange' 'section'}}>Section view</li>
|
||||
{{/if}}
|
||||
{{#if pinState.isPinned}}
|
||||
<li class="item" {{action 'onUnpin'}}>Unfavorite</li>
|
||||
{{else}}
|
||||
<li class="item" id="pin-document-button">Favorite</li>
|
||||
{{/if}}
|
||||
{{#if permissions.documentEdit}}
|
||||
<li class="item">{{#link-to 'document.history'}}History{{/link-to}}</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if permissions.documentTemplate}}
|
||||
<li class="item" id="save-template-button">Template</li>
|
||||
{{/if}}
|
||||
<li class="divider"/>
|
||||
<li class="item" id="print-document-button" {{action 'onPrintDocument'}}>Print</li>
|
||||
{{#if permissions.documentDelete}}
|
||||
<li class="item danger" id="delete-document-button">Delete</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
{{/dropdown-menu}}
|
||||
|
||||
{{#if session.authenticated}}
|
||||
{{#if menuOpen}}
|
||||
{{#unless pinState.isPinned}}
|
||||
{{#dropdown-dialog target="pin-document-button" position="bottom right" button="Pin" color="flat-green" onAction=(action 'onPin') focusOn="pin-document-name" }}
|
||||
<div class="input-control">
|
||||
<label>Favorite Document</label>
|
||||
<div class="tip">Provide short name</div>
|
||||
{{input type='text' id="pin-document-name" value=pinState.newName}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{#if permissions.documentDelete}}
|
||||
{{#if menuOpen}}
|
||||
{{#dropdown-dialog target="delete-document-button" position="bottom right" button="Delete" color="flat-red" onAction=(action 'onDeleteDocument')}}
|
||||
<p>Are you sure you want to delete this document?</p>
|
||||
<p>There is no undo, so be careful.</p>
|
||||
{{/dropdown-dialog}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if permissions.documentTemplate}}
|
||||
{{#if menuOpen}}
|
||||
{{#dropdown-dialog target="save-template-button" position="bottom right" button="Save as Template" color="flat-green" onAction=(action 'onSaveTemplate') focusOn="new-template-name" }}
|
||||
<div class="input-control">
|
||||
<label>Name</label>
|
||||
<div class="tip">Short name for this type of document</div>
|
||||
{{input type='text' id="new-template-name" value=saveTemplate.name}}
|
||||
</div>
|
||||
<div class="input-control">
|
||||
<label>Excerpt</label>
|
||||
<div class="tip">Explain use case for this template</div>
|
||||
{{textarea value=saveTemplate.description rows="3" id="new-template-desc"}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
|
@ -1,3 +1,5 @@
|
|||
{{document/document-attachments document=document permissions=permissions}}
|
||||
|
||||
<div class="document-view {{if (is-equal document.layout 'doc') 'document-view-unified'}}">
|
||||
|
||||
{{#if hasPages}}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
<div class="sidebar-panel">
|
||||
<div class="title">Attachments</div>
|
||||
<div class="document-sidebar-view-attachments">
|
||||
{{#if permissions.documentEdit}}
|
||||
<div id="upload-document-files" class="upload-document-files">
|
||||
Drag-drop files or click to select files
|
||||
</div>
|
||||
{{/if}}
|
||||
<ul class="list">
|
||||
{{#each files key="id" as |a index|}}
|
||||
<li class="item">
|
||||
<img class="icon" src="/assets/img/attachments/{{document/file-icon a.extension}}" />
|
||||
<a href="{{ appMeta.endpoint }}/public/attachments/{{ appMeta.orgId }}/{{ a.id }}">
|
||||
<span class="file">{{ a.filename }}</span>
|
||||
</a>
|
||||
{{#if permissions.documentEdit}}
|
||||
<div class="action round-button-mono">
|
||||
<i class="material-icons color-gray delete-attachment-{{a.id}}" title="Delete" {{action 'onConfirmDelete' a.id a.filename}}>delete</i>
|
||||
</div>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#if emptyState}}
|
||||
<div class="explainer">
|
||||
<div class="empty-state">
|
||||
There are no attachments
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="dropdown-dialog delete-attachment-dialog">
|
||||
<div class="content">
|
||||
<p>Are you sure you want to delete <span class="bold">{{deleteAttachment.name}}?</span></p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="flat-button" {{action 'onCancel'}}>
|
||||
cancel
|
||||
</div>
|
||||
<div class="flat-button flat-red" {{action 'onDelete'}}>
|
||||
delete
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,34 +0,0 @@
|
|||
<div class="sidebar-panel">
|
||||
<div class="title">Index</div>
|
||||
<div class="document-sidebar-view-index">
|
||||
<div class="structure">
|
||||
{{#if this.session.authenticated}}
|
||||
{{#unless emptyState}}
|
||||
<div id="tocToolbar" class="hidden-xs hidden-sm toc-controls {{if state.actionablePage 'current-page' ''}}">
|
||||
<div id="toc-up-button" class="round-button-mono {{if state.upDisabled 'disabled'}}" data-tooltip="Move up" data-tooltip-position="top center" {{action 'pageUp'}}>
|
||||
<i class="material-icons">arrow_upward</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-down-button" class="round-button-mono {{if state.downDisabled 'disabled'}}" data-tooltip="Move down" data-tooltip-position="top center" {{action 'pageDown'}}>
|
||||
<i class="material-icons">arrow_downward</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-outdent-button" class="round-button-mono {{if state.outdentDisabled 'disabled'}}" data-tooltip="Outdent" data-tooltip-position="top center" {{action 'pageOutdent'}}>
|
||||
<i class="material-icons">format_indent_decrease</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-indent-button" class="round-button-mono {{if state.indentDisabled 'disabled'}}" data-tooltip="Indent" data-tooltip-position="top center" {{action 'pageIndent'}}>
|
||||
<i class="material-icons">format_indent_increase</i>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
<ul class="index-list">
|
||||
{{#each pages key="id" as |p index|}}
|
||||
{{document/sidebar-view-index-entry page=p index=index onClick=(action 'onEntryClick')}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,118 +0,0 @@
|
|||
<div class="sidebar-toolbar">
|
||||
<div class="round-button-mono" id="sidebar-zone-more-button">
|
||||
<i class="material-icons">more_horiz</i>
|
||||
</div>
|
||||
<div class="margin-top-20"></div>
|
||||
<div class="round-button-mono {{if (is-equal tab 'index') 'selected'}}" {{action 'onChangeTab' 'index'}}>
|
||||
<i class="material-icons">view_headline</i>
|
||||
</div>
|
||||
<div class="margin-top-20"></div>
|
||||
<div class="round-button-mono {{if (is-equal tab 'attachments') 'selected'}}" {{action 'onChangeTab' 'attachments'}}>
|
||||
<i class="material-icons">attach_file</i>
|
||||
</div>
|
||||
{{#if permissions.documentEdit}}
|
||||
<div class="margin-top-20"></div>
|
||||
<div class="round-button-mono {{if (is-equal tab 'activity') 'selected'}}" {{action 'onChangeTab' 'activity'}}>
|
||||
<i class="material-icons">timeline</i>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="sidebar-common">
|
||||
{{#if document.template}}
|
||||
<div class="template-header">Template</div>
|
||||
{{/if}}
|
||||
{{document/tag-editor documentTags=document.tags permissions=permissions onChange=(action 'onTagChange')}}
|
||||
</div>
|
||||
|
||||
<div class="sidebar-wrapper">
|
||||
{{#if (is-equal tab 'index')}}
|
||||
{{document/sidebar-view-index document=document folder=folder pages=pages page=page permissions=permissions
|
||||
onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange') onGotoPage=(action 'onGotoPage')}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (is-equal tab 'attachments')}}
|
||||
{{document/sidebar-view-attachments document=document permissions=permissions}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (is-equal tab 'activity')}}
|
||||
{{document/sidebar-view-activity document=document pages=pages permissions=permissions}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#dropdown-menu target="sidebar-zone-more-button" position="bottom left" open="click" onOpenCallback=(action 'onMenuOpen') onCloseCallback=(action 'onMenuOpen')}}
|
||||
<ul class="menu">
|
||||
{{#if session.authenticated}}
|
||||
{{#if (is-equal document.layout 'section')}}
|
||||
<li class="item" {{action 'onLayoutChange' 'doc'}}>Flat view</li>
|
||||
<li class="divider"></li>
|
||||
{{else}}
|
||||
<li class="item" {{action 'onLayoutChange' 'section'}}>Section view</li>
|
||||
<li class="divider"></li>
|
||||
{{/if}}
|
||||
{{#if pinState.isPinned}}
|
||||
<li class="item" {{action 'onUnpin'}}>Unpin</li>
|
||||
{{else}}
|
||||
<li class="item" id="pin-document-button">Pin</li>
|
||||
{{/if}}
|
||||
{{#if permissions.documentEdit}}
|
||||
<li class="item">
|
||||
{{#link-to 'document.history'}}History{{/link-to}}
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if permissions.documentTemplate}}
|
||||
<li class="item" id="save-template-button">Template</li>
|
||||
<li class="divider"></li>
|
||||
{{/if}}
|
||||
|
||||
<li class="item" id="print-document-button" {{action 'onPrintDocument'}}>Print</li>
|
||||
|
||||
{{#if permissions.documentDelete}}
|
||||
<li class="divider"></li>
|
||||
<li class="item danger" id="delete-document-button">Delete</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
{{/dropdown-menu}}
|
||||
|
||||
{{#if session.authenticated}}
|
||||
{{#if menuOpen}}
|
||||
{{#unless pinState.isPinned}}
|
||||
{{#dropdown-dialog target="pin-document-button" position="bottom left" button="Pin" color="flat-green" onAction=(action 'onPin') focusOn="pin-document-name" }}
|
||||
<div class="input-control">
|
||||
<label>Pin Document</label>
|
||||
<div class="tip">A 3 or 4 character name</div>
|
||||
{{input type='text' id="pin-document-name" value=pinState.newName}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{#if permissions.documentDelete}}
|
||||
{{#if menuOpen}}
|
||||
{{#dropdown-dialog target="delete-document-button" position="bottom left" button="Delete" color="flat-red" onAction=(action 'onDeleteDocument')}}
|
||||
<p>Are you sure you want to delete this document?</p>
|
||||
<p>There is no undo, so be careful.</p>
|
||||
{{/dropdown-dialog}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if permissions.documentTemplate}}
|
||||
{{#if menuOpen}}
|
||||
{{#dropdown-dialog target="save-template-button" position="bottom left" button="Save as Template" color="flat-green" onAction=(action 'onSaveTemplate') focusOn="new-template-name" }}
|
||||
<div class="input-control">
|
||||
<label>Name</label>
|
||||
<div class="tip">Short name for this type of document</div>
|
||||
{{input type='text' id="new-template-name" value=saveTemplate.name}}
|
||||
</div>
|
||||
<div class="input-control">
|
||||
<label>Excerpt</label>
|
||||
<div class="tip">Explain use case for this template</div>
|
||||
{{textarea value=saveTemplate.description rows="3" id="new-template-desc"}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
14
gui/app/templates/components/document/space-category.hbs
Normal file
14
gui/app/templates/components/document/space-category.hbs
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div class="back-to-space">
|
||||
{{#link-to 'folder' folder.id folder.slug}}
|
||||
<div class="regular-button button-gray">
|
||||
<i class="material-icons">arrow_back</i>
|
||||
<div class="name">{{folder.name}}</div>
|
||||
</div>
|
||||
{{/link-to}}
|
||||
</div>
|
||||
|
||||
<div class="document-category hide">
|
||||
<div class="chip chip-action">
|
||||
<span id="upload-document-files" class="chip-text">+ category</span>
|
||||
</div>
|
||||
</div>
|
|
@ -9,7 +9,7 @@
|
|||
{{/each}}
|
||||
{{#if canAdd}}
|
||||
<div class="chip-action">
|
||||
<span id="add-tag-button" class="chip-text">add tag</span>
|
||||
<span id="add-tag-button" class="chip-text">+ tag</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">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="panel">
|
||||
<div class="form-header">
|
||||
<div class="title">Categories</div>
|
||||
<div class="tip">Organize and secure document access with optional categories</div>
|
||||
<div class="tip">Sub-divide spaces and secure document access with categories</div>
|
||||
</div>
|
||||
<form id="category-form" {{action 'onAdd' on='submit'}}>
|
||||
<div class="input-control">
|
||||
|
@ -14,17 +14,24 @@
|
|||
{{focus-input id=(concat 'edit-category-' cat.id) type="text" value=cat.category class="input-inline"}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="category">{{cat.category}}</div>
|
||||
<div class="category">
|
||||
<div class="name">{{cat.category}}</div>
|
||||
<div class="info">7 documents, 14 people</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="pull-right">
|
||||
<div class="pull-right buttons">
|
||||
{{#if cat.editMode}}
|
||||
<button type="submit" class="round-button-mono" {{action 'onSave' cat.id}}>
|
||||
<i class="material-icons color-green">check</i>
|
||||
</button>
|
||||
<div class="round-button-mono" {{action 'onCancel' cat.id}}>
|
||||
<div class="round-button-mono" {{action 'onEditCancel' cat.id}}>
|
||||
<i class="material-icons color-gray">close</i>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="">
|
||||
<div id="category-access-button-{{cat.id}}" data-tooltip="Set user access" data-tooltip-position="top center" class="action round-button-mono button-white" {{action 'onShowAccessPicker' cat.id}}>
|
||||
<i class="material-icons">person</i>
|
||||
</div>
|
||||
<div {{action 'onEdit' cat.id}} class="action round-button-mono button-white">
|
||||
<i class="material-icons">edit</i>
|
||||
</div>
|
||||
|
@ -34,6 +41,7 @@
|
|||
{{#dropdown-dialog target=(concat 'delete-category-' cat.id) position="bottom right" button="Delete" color="flat-red" onAction=(action 'onDelete' cat.id)}}
|
||||
<p>Are you sure you want to delete category <b>{{cat.category}}?</b></p>
|
||||
{{/dropdown-dialog}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -51,3 +59,18 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-dialog category-access-dialog">
|
||||
<div class="content">
|
||||
{{ui/ui-list-picker items=categoryUsers nameField='fullname'}}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="flat-button" {{action 'onGrantCancel'}}>
|
||||
cancel
|
||||
</div>
|
||||
<div class="flat-button flat-blue" {{action 'onGrantAccess'}}>
|
||||
grant access
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
{{#if permissions.spaceManage}}
|
||||
<div class="button-gap"></div>
|
||||
{{#link-to 'folder.settings' folder.id folder.slug}}{{model.document.name}}
|
||||
<div class="round-button button-blue" id="space-settings-button" data-tooltip="Manage permissions" data-tooltip-position="top center">
|
||||
<div class="round-button button-gray" id="space-settings-button" data-tooltip="Manage permissions" data-tooltip-position="top center">
|
||||
<i class="material-icons">settings</i>
|
||||
</div>
|
||||
{{/link-to}}
|
||||
|
|
12
gui/app/templates/components/ui/ui-list-picker.hbs
Normal file
12
gui/app/templates/components/ui/ui-list-picker.hbs
Normal file
|
@ -0,0 +1,12 @@
|
|||
<div class="widget-list-picker">
|
||||
<ul class="options">
|
||||
{{#each items as |item|}}
|
||||
<li class="option {{if item.selected 'selected'}}" {{action 'onToggle' item}}>
|
||||
<div class="text">{{get item nameField}}</div>
|
||||
{{#if item.selected}}
|
||||
<i class="material-icons">check</i>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
|
@ -40,6 +40,7 @@ const (
|
|||
EventTypeSpacePermission EventType = "changed-space-permissions"
|
||||
EventTypeSpaceJoin EventType = "joined-space"
|
||||
EventTypeSpaceInvite EventType = "invited-space"
|
||||
EventTypeCategoryPermission EventType = "changed-category-permissions"
|
||||
EventTypeSectionAdd EventType = "added-document-section"
|
||||
EventTypeSectionUpdate EventType = "updated-document-section"
|
||||
EventTypeSectionDelete EventType = "removed-document-section"
|
||||
|
|
|
@ -16,7 +16,7 @@ import "time"
|
|||
// Permission represents a permission for a space and is persisted to the database.
|
||||
type Permission struct {
|
||||
ID uint64 `json:"id"`
|
||||
OrgID string `json:"-"`
|
||||
OrgID string `json:"orgId"`
|
||||
Who string `json:"who"` // user, role
|
||||
WhoID string `json:"whoId"` // either a user or role ID
|
||||
Action Action `json:"action"` // view, edit, delete
|
||||
|
@ -49,6 +49,9 @@ const (
|
|||
DocumentCopy Action = "doc-copy"
|
||||
// DocumentTemplate means you can create, edit and delete document templates and content blocks
|
||||
DocumentTemplate Action = "doc-template"
|
||||
|
||||
// CategoryView action means you can view a category and documents therein
|
||||
CategoryView Action = "view"
|
||||
)
|
||||
|
||||
// Record represents space permissions for a user on a space.
|
||||
|
@ -178,3 +181,11 @@ func EncodeRecord(r Record, a Action) (p Permission) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// CategoryViewRequestModel represents who should be allowed to see a category.
|
||||
type CategoryViewRequestModel struct {
|
||||
OrgID string `json:"orgId"`
|
||||
SpaceID string `json:"folderId"`
|
||||
CategoryID string `json:"categoryID"`
|
||||
UserID string `json:"userId"`
|
||||
}
|
||||
|
|
|
@ -132,6 +132,8 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
|
|||
Add(rt, RoutePrefixPrivate, "category", []string{"POST", "OPTIONS"}, nil, category.Add)
|
||||
Add(rt, RoutePrefixPrivate, "category/{categoryID}", []string{"PUT", "OPTIONS"}, nil, category.Update)
|
||||
Add(rt, RoutePrefixPrivate, "category/{categoryID}", []string{"DELETE", "OPTIONS"}, nil, category.Delete)
|
||||
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, "users/{userID}/password", []string{"POST", "OPTIONS"}, nil, user.ChangePassword)
|
||||
Add(rt, RoutePrefixPrivate, "users", []string{"POST", "OPTIONS"}, nil, user.Add)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue