mirror of
https://github.com/documize/community.git
synced 2025-07-24 15:49:44 +02:00
Merge pull request #131 from documize/protection-approvals
Document approval and locking
This commit is contained in:
commit
c77b5c05ca
347 changed files with 81458 additions and 112104 deletions
22
README.md
22
README.md
|
@ -10,10 +10,10 @@ Documize is an intelligent document environment (IDE) for creating, securing and
|
|||
|
||||
## Why should I care?
|
||||
|
||||
Because maybe like us, you might be tired of:
|
||||
Because maybe like us, you're tired of:
|
||||
|
||||
* juggling WYSIWYG editors, wiki software and various document related solutions
|
||||
* playing document related email tennis with contributions, versions and feedback
|
||||
* juggling WYSIWYG editors, wiki software and other document related solutions
|
||||
* playing email tennis with documents, contributions, versions and feedback
|
||||
* sharing not-so-secure folders with external participants
|
||||
|
||||
Sound familiar? Read on.
|
||||
|
@ -38,17 +38,21 @@ Reusable templates and content blocks.
|
|||
|
||||
Documentation related tasking and delegation.
|
||||
|
||||
Integrations for embedding SaaS data within documents.
|
||||
Integrations for embedding SaaS data within documents, zero add-on/marketplace fees.
|
||||
|
||||
## What does it look like?
|
||||
|
||||
Here is the home screen showing all spaces.
|
||||
All spaces.
|
||||
|
||||

|
||||

|
||||
|
||||
Space view.
|
||||
|
||||

|
||||
|
||||
## Latest version
|
||||
|
||||
v1.55.0
|
||||
v1.56.0
|
||||
|
||||
## OS support
|
||||
|
||||
|
@ -62,8 +66,8 @@ Documize runs on the following:
|
|||
|
||||
Documize is built with the following technologies:
|
||||
|
||||
- EmberJS (v2.17.0)
|
||||
- Go (v1.9.2)
|
||||
- EmberJS (v2.18.0)
|
||||
- Go (v1.9.3)
|
||||
|
||||
...and supports the following databases:
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
|
|||
}
|
||||
|
||||
// assign permissions to space
|
||||
perms := []string{"view", "manage", "own", "doc-add", "doc-edit", "doc-delete", "doc-move", "doc-copy", "doc-template"}
|
||||
perms := []string{"view", "manage", "own", "doc-add", "doc-edit", "doc-delete", "doc-move", "doc-copy", "doc-template", "doc-approve"}
|
||||
for _, p := range perms {
|
||||
sql = fmt.Sprintf("insert into permission (orgid, who, whoid, action, scope, location, refid) values (\"%s\", 'user', \"%s\", \"%s\", 'object', 'space', \"%s\")", orgID, userID, p, labelID)
|
||||
_, err = runSQL(rt, sql)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
|
||||
## TODO
|
||||
## PENDING REMOVALS
|
||||
|
||||
1. Remove audit table
|
||||
2. Remove document.layout field ?
|
||||
NONE
|
||||
|
||||
## MYSQL ENCODING
|
||||
|
||||
|
|
32
core/database/scripts/autobuild/db_00017.sql
Normal file
32
core/database/scripts/autobuild/db_00017.sql
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* enterprise edition */
|
||||
|
||||
-- document needs proection and approval columns
|
||||
ALTER TABLE document ADD COLUMN `protection` INT NOT NULL DEFAULT 0 AFTER `template`;
|
||||
ALTER TABLE document ADD COLUMN `approval` INT NOT NULL DEFAULT 0 AFTER `protection`;
|
||||
|
||||
-- page workflow status
|
||||
ALTER TABLE page ADD COLUMN `status` INT NOT NULL DEFAULT 0 AFTER `revisions`;
|
||||
|
||||
-- links pending changes to another page
|
||||
ALTER TABLE page ADD COLUMN `relativeid` CHAR(16) DEFAULT '' NOT NULL COLLATE utf8_bin AFTER `status`;
|
||||
|
||||
-- useraction captures what is being actioned
|
||||
ALTER TABLE useraction ADD COLUMN `reftype` CHAR(1) DEFAULT 'D' NOT NULL COLLATE utf8_bin AFTER `iscomplete`;
|
||||
ALTER TABLE useraction ADD COLUMN `reftypeid` CHAR(16) NOT NULL COLLATE utf8_bin AFTER `reftype`;
|
||||
|
||||
-- useractivity usage expansion
|
||||
ALTER TABLE useractivity ADD COLUMN `documentid` CHAR(16) DEFAULT '' NOT NULL COLLATE utf8_bin AFTER `sourceid`;
|
||||
ALTER TABLE useractivity ADD COLUMN `pageid` CHAR(16) DEFAULT '' NOT NULL COLLATE utf8_bin AFTER `documentid`;
|
||||
UPDATE useractivity SET documentid=sourceid WHERE sourcetype=2;
|
||||
ALTER TABLE useractivity DROP COLUMN `sourceid`;
|
||||
CREATE INDEX idx_useractivity_1 ON useractivity(orgid,documentid,sourcetype);
|
||||
CREATE INDEX idx_useractivity_2 ON useractivity(orgid,documentid,userid,sourcetype);
|
||||
|
||||
-- clean-up
|
||||
DELETE FROM categorymember WHERE documentid NOT IN (SELECT refid FROM document);
|
||||
UPDATE page SET level=1 WHERE level=0;
|
||||
|
||||
-- deprecations
|
||||
DROP TABLE IF EXISTS `audit`;
|
||||
DROP TABLE IF EXISTS `search_old`;
|
||||
ALTER TABLE document DROP COLUMN `layout`;
|
1
core/env/logger.go
vendored
1
core/env/logger.go
vendored
|
@ -15,6 +15,7 @@ package env
|
|||
// Logger provides the interface for Documize compatible loggers.
|
||||
type Logger interface {
|
||||
Info(message string)
|
||||
Trace(message string)
|
||||
Error(message string, err error)
|
||||
// SetDB(l Logger, db *sqlx.DB) Logger
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.U
|
|||
activity.UserID = ctx.UserID
|
||||
activity.Created = time.Now().UTC()
|
||||
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
activity.OrgID, activity.UserID, activity.LabelID, activity.SourceID, activity.SourceType, activity.ActivityType, activity.Created)
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, documentid, pageid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
activity.OrgID, activity.UserID, activity.LabelID, activity.DocumentID, activity.PageID, activity.SourceType, activity.ActivityType, activity.Created)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute record user activity")
|
||||
|
@ -44,24 +44,28 @@ func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.U
|
|||
|
||||
// GetDocumentActivity returns the metadata for a specified document.
|
||||
func (s Scope) GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error) {
|
||||
qry := `SELECT a.id, a.created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.sourceid as documentid, a.activitytype,
|
||||
IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname
|
||||
qry := `SELECT a.id, DATE(a.created) as created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.documentid, a.pageid, a.activitytype,
|
||||
IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname,
|
||||
IFNULL(p.title, '') as pagetitle
|
||||
FROM useractivity a
|
||||
LEFT JOIN user u ON a.userid=u.refid
|
||||
WHERE a.orgid=? AND a.sourceid=? AND a.sourcetype=2
|
||||
LEFT JOIN page p ON a.pageid=p.refid
|
||||
WHERE a.orgid=? AND a.documentid=?
|
||||
AND a.userid != '0' AND a.userid != ''
|
||||
ORDER BY a.created DESC`
|
||||
|
||||
err = s.Runtime.Db.Select(&a, qry, ctx.OrgID, id)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "select document user activity")
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
a = []activity.DocumentActivity{}
|
||||
}
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
err = errors.Wrap(err, "select document user activity")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -234,9 +234,9 @@ func (s Scope) GetSpaceCategoryMembership(ctx domain.RequestContext, spaceID str
|
|||
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 refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
AND p.action='view' AND (r.userid=? OR r.userid='0')
|
||||
))
|
||||
ORDER BY documentid`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
|
||||
|
||||
|
|
|
@ -222,7 +222,7 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *domain.St
|
|||
|
||||
store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: newDocument.LabelID,
|
||||
SourceID: newDocument.RefID,
|
||||
DocumentID: newDocument.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeCreated})
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/request"
|
||||
|
@ -32,6 +33,8 @@ import (
|
|||
pm "github.com/documize/community/model/permission"
|
||||
"github.com/documize/community/model/search"
|
||||
"github.com/documize/community/model/space"
|
||||
"github.com/documize/community/model/user"
|
||||
"github.com/documize/community/model/workflow"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
|
@ -77,7 +80,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: document.LabelID,
|
||||
SourceID: document.RefID,
|
||||
DocumentID: document.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
|
@ -92,27 +95,6 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
response.WriteJSON(w, document)
|
||||
}
|
||||
|
||||
// Activity is an endpoint returning the activity logs for specified document.
|
||||
func (h *Handler) Activity(w http.ResponseWriter, r *http.Request) {
|
||||
method := "document.activity"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
id := request.Param(r, "documentID")
|
||||
if len(id) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
a, err := h.Store.Activity.GetDocumentActivity(ctx, id)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteJSON(w, a)
|
||||
}
|
||||
|
||||
// DocumentLinks is an endpoint returning the links for a document.
|
||||
func (h *Handler) DocumentLinks(w http.ResponseWriter, r *http.Request) {
|
||||
method := "document.links"
|
||||
|
@ -155,7 +137,7 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// get complete list of documents
|
||||
documents, err := h.Store.Document.GetBySpace(ctx, spaceID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
|
@ -164,6 +146,8 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
|
|||
documents = []doc.Document{}
|
||||
}
|
||||
|
||||
sort.Sort(doc.ByTitle(documents))
|
||||
|
||||
// remove documents that cannot be seen due to lack of
|
||||
// category view/access permission
|
||||
filtered := []doc.Document{}
|
||||
|
@ -279,11 +263,6 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if !permission.CanDeleteDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -291,6 +270,35 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// If locked document then no can do
|
||||
if doc.Protection == workflow.ProtectionLock {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted action on locked document")
|
||||
return
|
||||
}
|
||||
|
||||
// If approval workflow then only approvers can delete page
|
||||
if doc.Protection == workflow.ProtectionReview {
|
||||
approvers, err := permission.GetDocumentApprovers(ctx, *h.Store, doc.LabelID, doc.RefID)
|
||||
if err != nil {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !user.Exists(approvers, ctx.UserID) {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted action on document when not approver")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// permission check
|
||||
if !permission.CanDeleteDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -319,7 +327,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: documentID,
|
||||
DocumentID: documentID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
|
||||
|
@ -411,9 +419,18 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
if len(perms) == 0 {
|
||||
perms = []pm.Permission{}
|
||||
}
|
||||
|
||||
record := pm.DecodeUserPermissions(perms)
|
||||
|
||||
roles, err := h.Store.Permission.GetUserDocumentPermissions(ctx, document.RefID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(roles) == 0 {
|
||||
roles = []pm.Permission{}
|
||||
}
|
||||
rolesRecord := pm.DecodeUserDocumentPermissions(roles)
|
||||
|
||||
// links
|
||||
l, err := h.Store.Link.GetDocumentOutboundLinks(ctx, id)
|
||||
if len(l) == 0 {
|
||||
|
@ -436,9 +453,10 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
sp = []space.Space{}
|
||||
}
|
||||
|
||||
data := documentData{}
|
||||
data := BulkDocumentData{}
|
||||
data.Document = document
|
||||
data.Permissions = record
|
||||
data.Roles = rolesRecord
|
||||
data.Links = l
|
||||
data.Spaces = sp
|
||||
|
||||
|
@ -451,7 +469,7 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: document.LabelID,
|
||||
SourceID: document.RefID,
|
||||
DocumentID: document.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
|
@ -466,11 +484,12 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
response.WriteJSON(w, data)
|
||||
}
|
||||
|
||||
// documentData represents all data associated for a single document.
|
||||
// BulkDocumentData represents all data associated for a single document.
|
||||
// Used by FetchDocumentData() bulk data load call.
|
||||
type documentData struct {
|
||||
Document doc.Document `json:"document"`
|
||||
Permissions pm.Record `json:"permissions"`
|
||||
Spaces []space.Space `json:"folders"`
|
||||
Links []link.Link `json:"link"`
|
||||
type BulkDocumentData struct {
|
||||
Document doc.Document `json:"document"`
|
||||
Permissions pm.Record `json:"permissions"`
|
||||
Roles pm.DocumentRecord `json:"roles"`
|
||||
Spaces []space.Space `json:"folders"`
|
||||
Links []link.Link `json:"links"`
|
||||
}
|
||||
|
|
|
@ -34,8 +34,8 @@ func (s Scope) Add(ctx domain.RequestContext, document doc.Document) (err error)
|
|||
document.Created = time.Now().UTC()
|
||||
document.Revised = document.Created // put same time in both fields
|
||||
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO document (refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
document.RefID, document.OrgID, document.LabelID, document.UserID, document.Job, document.Location, document.Title, document.Excerpt, document.Slug, document.Tags, document.Template, document.Created, document.Revised)
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO document (refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
document.RefID, document.OrgID, document.LabelID, document.UserID, document.Job, document.Location, document.Title, document.Excerpt, document.Slug, document.Tags, document.Template, document.Protection, document.Approval, document.Created, document.Revised)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execuet insert document")
|
||||
|
@ -46,7 +46,7 @@ func (s Scope) Add(ctx domain.RequestContext, document doc.Document) (err error)
|
|||
|
||||
// Get fetches the document record with the given id fromt the document table and audits that it has been got.
|
||||
func (s Scope) Get(ctx domain.RequestContext, id string) (document doc.Document, err error) {
|
||||
err = s.Runtime.Db.Get(&document, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? and refid=?",
|
||||
err = s.Runtime.Db.Get(&document, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? and refid=?",
|
||||
ctx.OrgID, id)
|
||||
|
||||
if err != nil {
|
||||
|
@ -90,9 +90,9 @@ func (s Scope) DocumentMeta(ctx domain.RequestContext, id string) (meta doc.Docu
|
|||
return
|
||||
}
|
||||
|
||||
// GetAll returns a slice containg all of the the documents for the client's organisation, with the most recient first.
|
||||
// GetAll returns a slice containg all of the the documents for the client's organisation.
|
||||
func (s Scope) GetAll() (ctx domain.RequestContext, 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 ORDER BY revised DESC", ctx.OrgID)
|
||||
err = s.Runtime.Db.Select(&documents, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? AND template=0 ORDER BY title", ctx.OrgID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "select documents")
|
||||
|
@ -106,18 +106,21 @@ func (s Scope) GetAll() (ctx domain.RequestContext, documents []doc.Document, er
|
|||
// 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
|
||||
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, 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 refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
AND p.who='role' AND p.location='space' AND p.refid=? AND p.action='view' AND (r.userid=? OR r.userid='0')
|
||||
))
|
||||
)
|
||||
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "select documents by space")
|
||||
}
|
||||
|
@ -128,14 +131,14 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
|
|||
// 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) {
|
||||
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=1
|
||||
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? AND template=1
|
||||
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'
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
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=? OR r.userid='0')
|
||||
))
|
||||
)
|
||||
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
|
||||
|
@ -150,14 +153,14 @@ func (s Scope) Templates(ctx domain.RequestContext) (documents []doc.Document, e
|
|||
// TemplatesBySpace returns a slice containing the documents available as templates for given space.
|
||||
func (s Scope) TemplatesBySpace(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 labelid=? AND template=1
|
||||
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? AND labelid=? AND template=1
|
||||
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'
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
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=? OR r.userid='0')
|
||||
))
|
||||
)
|
||||
ORDER BY title`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
|
||||
|
@ -193,14 +196,14 @@ func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documen
|
|||
// DocumentList returns a slice containing the documents available as templates to the client's organisation, in title order.
|
||||
func (s Scope) DocumentList(ctx domain.RequestContext) (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
|
||||
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, 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 IN (
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space'
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
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=? OR r.userid='0')
|
||||
))
|
||||
)
|
||||
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
|
||||
|
@ -221,7 +224,7 @@ func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document
|
|||
func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err error) {
|
||||
document.Revised = time.Now().UTC()
|
||||
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE document SET labelid=:labelid, userid=:userid, job=:job, location=:location, title=:title, excerpt=:excerpt, slug=:slug, tags=:tags, template=:template, layout=:layout, revised=:revised WHERE orgid=:orgid AND refid=:refid",
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE document SET labelid=:labelid, userid=:userid, job=:job, location=:location, title=:title, excerpt=:excerpt, slug=:slug, tags=:tags, template=:template, protection=:protection, approval=:approval, revised=:revised WHERE orgid=:orgid AND refid=:refid",
|
||||
&document)
|
||||
|
||||
if err != nil {
|
||||
|
@ -277,6 +280,11 @@ func (s Scope) Delete(ctx domain.RequestContext, documentID string) (rows int64,
|
|||
return
|
||||
}
|
||||
|
||||
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE from categorymember WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return b.DeleteConstrained(ctx.Transaction, "document", ctx.OrgID, documentID)
|
||||
}
|
||||
|
||||
|
|
|
@ -164,9 +164,10 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
|
|||
(
|
||||
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'
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
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=? OR r.userid='0')
|
||||
))
|
||||
)
|
||||
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
|
||||
|
@ -201,9 +202,10 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
|
|||
(
|
||||
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'
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
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=? OR r.userid='0')
|
||||
))
|
||||
)
|
||||
ORDER BY p.title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
|
||||
|
@ -238,9 +240,10 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
|
|||
(
|
||||
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'
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
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=? OR r.userid='0')
|
||||
))
|
||||
)
|
||||
ORDER BY a.filename`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
|
||||
|
|
98
domain/mail/document-approver.html
Normal file
98
domain/mail/document-approver.html
Normal file
|
@ -0,0 +1,98 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>{{.Subject}}</title>
|
||||
<style type="text/css">
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6;
|
||||
}
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
@media only screen and (max-width: 640px) {
|
||||
h1 {
|
||||
font-weight: 600 !important; margin: 20px 0 5px !important;
|
||||
}
|
||||
h2 {
|
||||
font-weight: 600 !important; margin: 20px 0 5px !important;
|
||||
}
|
||||
h3 {
|
||||
font-weight: 600 !important; margin: 20px 0 5px !important;
|
||||
}
|
||||
h4 {
|
||||
font-weight: 600 !important; margin: 20px 0 5px !important;
|
||||
}
|
||||
h1 {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
h2 {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
.container {
|
||||
width: 100% !important;
|
||||
}
|
||||
.content {
|
||||
padding: 10px !important;
|
||||
}
|
||||
.content-wrap {
|
||||
padding: 10px !important;
|
||||
}
|
||||
.invoice {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6; background: #f6f6f6; margin: 0; padding: 0;">
|
||||
|
||||
<table class="body-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background: #f6f6f6; margin: 0; padding: 0;">
|
||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||
<td style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0;" valign="top"></td>
|
||||
<td class="container" width="600" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto; padding: 0;" valign="top">
|
||||
<div class="content" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
|
||||
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
||||
Document Approval Role Granted
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
||||
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||
<p>You are requested to approve all changes to the following document:</p>
|
||||
<p style="font-weight: bold;">{{.Document}}</p>
|
||||
<p>{{.Inviter}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||
<a href="{{.Url}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">View document</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
|
||||
Have any questions? <a href="mailto:team@documize.com" style="color: #7a8184;">Contact Documize</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0;" valign="top"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
70
domain/mail/document.go
Normal file
70
domain/mail/document.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
// 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
|
||||
|
||||
// jshint ignore:start
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/documize/community/server/web"
|
||||
)
|
||||
|
||||
// DocumentApprover notifies user who has just been granted document approval rights.
|
||||
func (m *Mailer) DocumentApprover(recipient, inviter, url, document string) {
|
||||
method := "DocumentApprover"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/document-approver.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has granted you document approval", inviter)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
Document string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
document,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
|
@ -14,15 +14,11 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/server/web"
|
||||
)
|
||||
|
||||
// Mailer provides emailing facilities
|
||||
|
@ -33,241 +29,6 @@ type Mailer struct {
|
|||
Credentials Credentials
|
||||
}
|
||||
|
||||
// InviteNewUser invites someone new providing credentials, explaining the product and stating who is inviting them.
|
||||
func (m *Mailer) InviteNewUser(recipient, inviter, url, username, password string) {
|
||||
method := "InviteNewUser"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/invite-new-user.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has invited you to Documize", inviter)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
Username string
|
||||
Password string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
recipient,
|
||||
password,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
||||
|
||||
// InviteExistingUser invites a known user to an organization.
|
||||
func (m *Mailer) InviteExistingUser(recipient, inviter, url string) {
|
||||
method := "InviteExistingUser"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/invite-existing-user.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has invited you to their Documize account", inviter)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
||||
|
||||
// PasswordReset sends a reset email with an embedded token.
|
||||
func (m *Mailer) PasswordReset(recipient, url string) {
|
||||
method := "PasswordReset"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/password-reset.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
subject := "Documize password reset request"
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender //e.g. "Documize <hello@documize.com>"
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Url string
|
||||
}{
|
||||
subject,
|
||||
url,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
||||
|
||||
// ShareSpaceExistingUser provides an existing user with a link to a newly shared space.
|
||||
func (m *Mailer) ShareSpaceExistingUser(recipient, inviter, url, folder, intro string) {
|
||||
method := "ShareSpaceExistingUser"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/share-space-existing-user.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has shared %s with you", inviter, folder)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
Folder string
|
||||
Intro string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
folder,
|
||||
intro,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
||||
|
||||
// ShareSpaceNewUser invites new user providing Credentials, explaining the product and stating who is inviting them.
|
||||
func (m *Mailer) ShareSpaceNewUser(recipient, inviter, url, space, invitationMessage string) {
|
||||
method := "ShareSpaceNewUser"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/share-space-new-user.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, space)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
Invitation string
|
||||
Folder string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
invitationMessage,
|
||||
space,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Credentials holds SMTP endpoint and authentication methods
|
||||
type Credentials struct {
|
||||
SMTPuserid string
|
||||
|
|
122
domain/mail/space.go
Normal file
122
domain/mail/space.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
// 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
|
||||
|
||||
// jshint ignore:start
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/documize/community/server/web"
|
||||
)
|
||||
|
||||
// ShareSpaceExistingUser provides an existing user with a link to a newly shared space.
|
||||
func (m *Mailer) ShareSpaceExistingUser(recipient, inviter, url, folder, intro string) {
|
||||
method := "ShareSpaceExistingUser"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/share-space-existing-user.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has shared %s with you", inviter, folder)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
Folder string
|
||||
Intro string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
folder,
|
||||
intro,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
||||
|
||||
// ShareSpaceNewUser invites new user providing Credentials, explaining the product and stating who is inviting them.
|
||||
func (m *Mailer) ShareSpaceNewUser(recipient, inviter, url, space, invitationMessage string) {
|
||||
method := "ShareSpaceNewUser"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/share-space-new-user.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, space)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
Invitation string
|
||||
Folder string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
invitationMessage,
|
||||
space,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
157
domain/mail/user.go
Normal file
157
domain/mail/user.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
// 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
|
||||
|
||||
// jshint ignore:start
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/documize/community/server/web"
|
||||
)
|
||||
|
||||
// InviteNewUser invites someone new providing credentials, explaining the product and stating who is inviting them.
|
||||
func (m *Mailer) InviteNewUser(recipient, inviter, url, username, password string) {
|
||||
method := "InviteNewUser"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/invite-new-user.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has invited you to Documize", inviter)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
Username string
|
||||
Password string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
recipient,
|
||||
password,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
||||
|
||||
// InviteExistingUser invites a known user to an organization.
|
||||
func (m *Mailer) InviteExistingUser(recipient, inviter, url string) {
|
||||
method := "InviteExistingUser"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/invite-existing-user.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
// check inviter name
|
||||
if inviter == "Hello You" || len(inviter) == 0 {
|
||||
inviter = "Your colleague"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s has invited you to their Documize account", inviter)
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
Url string
|
||||
}{
|
||||
subject,
|
||||
inviter,
|
||||
url,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
||||
|
||||
// PasswordReset sends a reset email with an embedded token.
|
||||
func (m *Mailer) PasswordReset(recipient, url string) {
|
||||
method := "PasswordReset"
|
||||
m.LoadCredentials()
|
||||
|
||||
file, err := web.ReadFile("mail/password-reset.html")
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
|
||||
subject := "Documize password reset request"
|
||||
|
||||
e := NewEmail()
|
||||
e.From = m.Credentials.SMTPsender //e.g. "Documize <hello@documize.com>"
|
||||
e.To = []string{recipient}
|
||||
e.Subject = subject
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Url string
|
||||
}{
|
||||
subject,
|
||||
url,
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t.Execute(buffer, ¶meters)
|
||||
e.HTML = buffer.Bytes()
|
||||
|
||||
err = e.Send(m.GetHost(), m.GetAuth())
|
||||
if err != nil {
|
||||
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||
}
|
||||
}
|
|
@ -31,8 +31,11 @@ import (
|
|||
"github.com/documize/community/domain/section/provider"
|
||||
"github.com/documize/community/model/activity"
|
||||
"github.com/documize/community/model/audit"
|
||||
"github.com/documize/community/model/doc"
|
||||
dm "github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/page"
|
||||
pm "github.com/documize/community/model/permission"
|
||||
"github.com/documize/community/model/user"
|
||||
"github.com/documize/community/model/workflow"
|
||||
htmldiff "github.com/documize/html-diff"
|
||||
)
|
||||
|
||||
|
@ -53,17 +56,14 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// check param
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// read payload
|
||||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
|
@ -90,6 +90,39 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check protection and approval process
|
||||
document, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Protect locked
|
||||
if document.Protection == workflow.ProtectionLock {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted write to locked document")
|
||||
return
|
||||
}
|
||||
|
||||
// Check edit permission
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// if document review process then we must mark page as pending
|
||||
if document.Protection == workflow.ProtectionReview {
|
||||
if model.Page.RelativeID == "" {
|
||||
model.Page.Status = workflow.ChangePendingNew
|
||||
} else {
|
||||
model.Page.Status = workflow.ChangePending
|
||||
}
|
||||
} else {
|
||||
model.Page.RelativeID = ""
|
||||
model.Page.Status = workflow.ChangePublished
|
||||
}
|
||||
|
||||
pageID := uniqueid.Generate()
|
||||
model.Page.RefID = pageID
|
||||
model.Meta.PageID = pageID
|
||||
|
@ -97,7 +130,6 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
model.Meta.UserID = ctx.UserID // required for Render call below
|
||||
model.Page.SetDefaults()
|
||||
model.Meta.SetDefaults()
|
||||
// page.Title = template.HTMLEscapeString(page.Title)
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
|
@ -134,8 +166,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: model.Page.DocumentID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
DocumentID: model.Page.DocumentID,
|
||||
PageID: model.Page.RefID,
|
||||
SourceType: activity.SourceTypePage,
|
||||
ActivityType: activity.TypeCreated})
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionAdd)
|
||||
|
@ -229,16 +262,11 @@ func (h *Handler) GetPages(w http.ResponseWriter, r *http.Request) {
|
|||
response.WriteJSON(w, pages)
|
||||
}
|
||||
|
||||
// Delete deletes a page.
|
||||
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.delete"
|
||||
// GetMeta gets page meta data for specified document page.
|
||||
func (h *Handler) GetMeta(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.meta"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !h.Runtime.Product.License.IsValid() {
|
||||
response.WriteBadLicense(w)
|
||||
return
|
||||
}
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
|
@ -251,156 +279,31 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
meta, err := h.Store.Page.GetPageMeta(ctx, pageID)
|
||||
if err == sql.ErrNoRows {
|
||||
response.WriteNotFoundError(w, method, pageID)
|
||||
h.Runtime.Log.Info(method + " no record")
|
||||
meta = page.Meta{}
|
||||
response.WriteJSON(w, meta)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
if meta.DocumentID != documentID {
|
||||
response.WriteBadRequestError(w, method, "documentID mismatch")
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
page, err := h.Store.Page.Get(ctx, pageID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(page.BlockID) > 0 {
|
||||
h.Store.Block.DecrementUsage(ctx, page.BlockID)
|
||||
}
|
||||
|
||||
_, err = h.Store.Page.Delete(ctx, documentID, pageID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: documentID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
|
||||
|
||||
go h.Indexer.DeleteContent(ctx, pageID)
|
||||
|
||||
h.Store.Link.DeleteSourcePageLinks(ctx, pageID)
|
||||
|
||||
h.Store.Link.MarkOrphanPageLink(ctx, pageID)
|
||||
|
||||
h.Store.Page.DeletePageRevisions(ctx, pageID)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// DeletePages batch deletes pages.
|
||||
func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.delete.pages"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !h.Runtime.Product.License.IsValid() {
|
||||
response.WriteBadLicense(w)
|
||||
return
|
||||
}
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "Bad body")
|
||||
return
|
||||
}
|
||||
|
||||
model := new([]page.LevelRequest)
|
||||
err = json.Unmarshal(body, &model)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "JSON marshal")
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, page := range *model {
|
||||
pageData, err := h.Store.Page.Get(ctx, page.PageID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(pageData.BlockID) > 0 {
|
||||
h.Store.Block.DecrementUsage(ctx, pageData.BlockID)
|
||||
}
|
||||
|
||||
_, err = h.Store.Page.Delete(ctx, documentID, page.PageID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
go h.Indexer.DeleteContent(ctx, page.PageID)
|
||||
|
||||
h.Store.Link.DeleteSourcePageLinks(ctx, page.PageID)
|
||||
|
||||
h.Store.Link.MarkOrphanPageLink(ctx, page.PageID)
|
||||
|
||||
h.Store.Page.DeletePageRevisions(ctx, page.PageID)
|
||||
}
|
||||
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: documentID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
response.WriteJSON(w, meta)
|
||||
}
|
||||
|
||||
// Update will persist changed page and note the fact
|
||||
|
@ -415,11 +318,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// if !ctx.Editor {
|
||||
// response.WriteForbiddenError(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// Check params
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
|
@ -432,11 +331,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Read payload
|
||||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
|
@ -458,13 +353,26 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check protection and approval process
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if doc.Protection == workflow.ProtectionLock {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted write to locked document")
|
||||
return
|
||||
}
|
||||
|
||||
// Check edit permission
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -492,6 +400,11 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
skipRevision := false
|
||||
skipRevision, err = strconv.ParseBool(request.Query(r, "r"))
|
||||
|
||||
// We don't track revisions for non-published pages
|
||||
if model.Page.Status != workflow.ChangePublished {
|
||||
skipRevision = true
|
||||
}
|
||||
|
||||
err = h.Store.Page.Update(ctx, model.Page, refID, ctx.UserID, skipRevision)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -510,8 +423,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: model.Page.DocumentID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
DocumentID: model.Page.DocumentID,
|
||||
PageID: model.Page.RefID,
|
||||
SourceType: activity.SourceTypePage,
|
||||
ActivityType: activity.TypeEdited})
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionUpdate)
|
||||
|
@ -562,6 +476,211 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
response.WriteJSON(w, updatedPage)
|
||||
}
|
||||
|
||||
// Delete a page.
|
||||
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.delete"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !h.Runtime.Product.License.IsValid() {
|
||||
response.WriteBadLicense(w)
|
||||
return
|
||||
}
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
pageID := request.Param(r, "pageID")
|
||||
if len(pageID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "pageID")
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := h.Store.Page.Get(ctx, pageID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// you can delete your own pending page
|
||||
ownPending := false
|
||||
if (p.Status == workflow.ChangePending || p.Status == workflow.ChangePendingNew) && p.UserID == ctx.UserID {
|
||||
ownPending = true
|
||||
}
|
||||
|
||||
// if not own page then check permission
|
||||
if !ownPending {
|
||||
ok, _ := h.workflowPermitsChange(doc, ctx)
|
||||
if !ok {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted delete section on locked document")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(p.BlockID) > 0 {
|
||||
h.Store.Block.DecrementUsage(ctx, p.BlockID)
|
||||
}
|
||||
|
||||
_, err = h.Store.Page.Delete(ctx, documentID, pageID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
DocumentID: documentID,
|
||||
PageID: pageID,
|
||||
SourceType: activity.SourceTypePage,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
|
||||
go h.Indexer.DeleteContent(ctx, pageID)
|
||||
|
||||
h.Store.Link.DeleteSourcePageLinks(ctx, pageID)
|
||||
|
||||
h.Store.Link.MarkOrphanPageLink(ctx, pageID)
|
||||
|
||||
h.Store.Page.DeletePageRevisions(ctx, pageID)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
|
||||
|
||||
// Re-level all pages in document
|
||||
h.LevelizeDocument(ctx, documentID)
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// DeletePages batch deletes pages.
|
||||
func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.delete.pages"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !h.Runtime.Product.License.IsValid() {
|
||||
response.WriteBadLicense(w)
|
||||
return
|
||||
}
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "Bad body")
|
||||
return
|
||||
}
|
||||
|
||||
model := new([]page.LevelRequest)
|
||||
err = json.Unmarshal(body, &model)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "JSON marshal")
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, page := range *model {
|
||||
pageData, err := h.Store.Page.Get(ctx, page.PageID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ownPending := false
|
||||
if (pageData.Status == workflow.ChangePending || pageData.Status == workflow.ChangePendingNew) && pageData.UserID == ctx.UserID {
|
||||
ownPending = true
|
||||
}
|
||||
|
||||
// if not own page then check permission
|
||||
if !ownPending {
|
||||
ok, _ := h.workflowPermitsChange(doc, ctx)
|
||||
if !ok {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted delete section on locked document")
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(pageData.BlockID) > 0 {
|
||||
h.Store.Block.DecrementUsage(ctx, pageData.BlockID)
|
||||
}
|
||||
|
||||
_, err = h.Store.Page.Delete(ctx, documentID, page.PageID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
go h.Indexer.DeleteContent(ctx, page.PageID)
|
||||
|
||||
h.Store.Link.DeleteSourcePageLinks(ctx, page.PageID)
|
||||
|
||||
h.Store.Link.MarkOrphanPageLink(ctx, page.PageID)
|
||||
|
||||
h.Store.Page.DeletePageRevisions(ctx, page.PageID)
|
||||
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
DocumentID: documentID,
|
||||
PageID: page.PageID,
|
||||
SourceType: activity.SourceTypePage,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
|
||||
|
||||
// Re-level all pages in document
|
||||
h.LevelizeDocument(ctx, documentID)
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
//**************************************************
|
||||
// Table of Contents
|
||||
//**************************************************
|
||||
|
||||
// ChangePageSequence will swap page sequence for a given number of pages.
|
||||
func (h *Handler) ChangePageSequence(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.sequence"
|
||||
|
@ -578,8 +697,17 @@ func (h *Handler) ChangePageSequence(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ok, err := h.workflowPermitsChange(doc, ctx)
|
||||
if !ok {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted to chaneg page sequence on protected document")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -638,9 +766,17 @@ func (h *Handler) ChangePageLevel(w http.ResponseWriter, r *http.Request) {
|
|||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
ok, err := h.workflowPermitsChange(doc, ctx)
|
||||
if !ok {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted to chaneg page level on protected document")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -684,75 +820,10 @@ func (h *Handler) ChangePageLevel(w http.ResponseWriter, r *http.Request) {
|
|||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// GetMeta gets page meta data for specified document page.
|
||||
func (h *Handler) GetMeta(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.meta"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
pageID := request.Param(r, "pageID")
|
||||
if len(pageID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "pageID")
|
||||
return
|
||||
}
|
||||
|
||||
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
meta, err := h.Store.Page.GetPageMeta(ctx, pageID)
|
||||
if err == sql.ErrNoRows {
|
||||
response.WriteNotFoundError(w, method, pageID)
|
||||
h.Runtime.Log.Info(method + " no record")
|
||||
meta = page.Meta{}
|
||||
response.WriteJSON(w, meta)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
if meta.DocumentID != documentID {
|
||||
response.WriteBadRequestError(w, method, "documentID mismatch")
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteJSON(w, meta)
|
||||
}
|
||||
|
||||
//**************************************************
|
||||
// Copy Move Page
|
||||
//**************************************************
|
||||
|
||||
// GetMoveCopyTargets returns available documents for page copy/move axction.
|
||||
func (h *Handler) GetMoveCopyTargets(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.targets"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
var d []doc.Document
|
||||
var err error
|
||||
|
||||
d, err = h.Store.Document.DocumentList(ctx)
|
||||
if len(d) == 0 {
|
||||
d = []doc.Document{}
|
||||
}
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteJSON(w, d)
|
||||
}
|
||||
|
||||
// Copy copies page to either same or different document.
|
||||
func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.targets"
|
||||
|
@ -790,6 +861,12 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// workflow check
|
||||
if doc.Protection == workflow.ProtectionLock || doc.Protection == workflow.ProtectionReview {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := h.Store.Page.Get(ctx, pageID)
|
||||
if err == sql.ErrNoRows {
|
||||
response.WriteNotFoundError(w, method, documentID)
|
||||
|
@ -850,9 +927,10 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
|
|||
// Log action against target document
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: targetID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeEdited})
|
||||
DocumentID: targetID,
|
||||
PageID: newPageID,
|
||||
SourceType: activity.SourceTypePage,
|
||||
ActivityType: activity.TypeCreated})
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionCopy)
|
||||
|
||||
|
@ -993,6 +1071,7 @@ func (h *Handler) GetDiff(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
res, err := cfg.HTMLdiff([]string{latestHTML, previousHTML})
|
||||
// res, err := cfg.HTMLdiff([]string{previousHTML, latestHTML})
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
|
@ -1025,12 +1104,20 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ok, err := h.workflowPermitsChange(doc, ctx)
|
||||
if !ok {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Info("attempted to chaneg page sequence on protected document")
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -1058,13 +1145,6 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// roll back page
|
||||
p.Body = revision.Body
|
||||
refID := uniqueid.Generate()
|
||||
|
@ -1091,8 +1171,9 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: p.DocumentID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
DocumentID: p.DocumentID,
|
||||
PageID: p.RefID,
|
||||
SourceType: activity.SourceTypePage,
|
||||
ActivityType: activity.TypeReverted})
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionRollback)
|
||||
|
@ -1101,3 +1182,216 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
response.WriteJSON(w, p)
|
||||
}
|
||||
|
||||
//**************************************************
|
||||
// Bulk data fetching (reduce network traffic)
|
||||
//**************************************************
|
||||
|
||||
// FetchPages returns all page data for given document: page, meta data, pending changes.
|
||||
func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
||||
method := "page.FetchPages"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
model := []page.BulkRequest{}
|
||||
|
||||
// check params
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// published pages and new pages awaiting approval
|
||||
pages, err := h.Store.Page.GetPages(ctx, documentID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
if len(pages) == 0 {
|
||||
pages = []page.Page{}
|
||||
}
|
||||
|
||||
// unpublished pages
|
||||
unpublished, err := h.Store.Page.GetUnpublishedPages(ctx, documentID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
if len(unpublished) == 0 {
|
||||
unpublished = []page.Page{}
|
||||
}
|
||||
|
||||
// meta for all pages
|
||||
meta, err := h.Store.Page.GetDocumentPageMeta(ctx, documentID, false)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
if len(meta) == 0 {
|
||||
meta = []page.Meta{}
|
||||
}
|
||||
|
||||
// permissions
|
||||
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, doc.LabelID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(perms) == 0 {
|
||||
perms = []pm.Permission{}
|
||||
}
|
||||
permissions := pm.DecodeUserPermissions(perms)
|
||||
|
||||
roles, err := h.Store.Permission.GetUserDocumentPermissions(ctx, doc.RefID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(roles) == 0 {
|
||||
roles = []pm.Permission{}
|
||||
}
|
||||
docRoles := pm.DecodeUserDocumentPermissions(roles)
|
||||
|
||||
// check document view permissions
|
||||
if !permissions.SpaceView && !permissions.SpaceManage && !permissions.SpaceOwner {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// process published pages
|
||||
for _, p := range pages {
|
||||
// only send back pages that user can see
|
||||
process := false
|
||||
forcePending := false
|
||||
|
||||
if process == false && p.Status == workflow.ChangePublished {
|
||||
process = true
|
||||
}
|
||||
if process == false && p.Status == workflow.ChangePendingNew && p.RelativeID == "" && p.UserID == ctx.UserID {
|
||||
process = true
|
||||
forcePending = true // user has newly created page which should be treated as pending
|
||||
}
|
||||
if process == false && p.Status == workflow.ChangeUnderReview && p.RelativeID == "" && p.UserID == ctx.UserID {
|
||||
process = true
|
||||
forcePending = true // user has newly created page which should be treated as pending
|
||||
}
|
||||
if process == false && p.Status == workflow.ChangeUnderReview && p.RelativeID == "" && (permissions.DocumentApprove || docRoles.DocumentRoleApprove) {
|
||||
process = true
|
||||
forcePending = true // user has newly created page which should be treated as pending
|
||||
}
|
||||
|
||||
if process {
|
||||
d := page.BulkRequest{}
|
||||
d.ID = fmt.Sprintf("container-%s", p.RefID)
|
||||
d.Page = p
|
||||
|
||||
for _, m := range meta {
|
||||
if p.RefID == m.PageID {
|
||||
d.Meta = m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
d.Pending = []page.PendingPage{}
|
||||
|
||||
// process pending pages
|
||||
for _, up := range unpublished {
|
||||
if up.RelativeID == p.RefID {
|
||||
ud := page.PendingPage{}
|
||||
ud.Page = up
|
||||
|
||||
for _, m := range meta {
|
||||
if up.RefID == m.PageID {
|
||||
ud.Meta = m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
owner, err := h.Store.User.Get(ctx, up.UserID)
|
||||
if err == nil {
|
||||
ud.Owner = owner.Fullname()
|
||||
}
|
||||
|
||||
d.Pending = append(d.Pending, ud)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle situation where we need approval, and user has created new page
|
||||
if forcePending && len(d.Pending) == 0 && doc.Protection == workflow.ProtectionReview {
|
||||
ud := page.PendingPage{}
|
||||
ud.Page = d.Page
|
||||
ud.Meta = d.Meta
|
||||
|
||||
owner, err := h.Store.User.Get(ctx, d.Page.UserID)
|
||||
if err == nil {
|
||||
ud.Owner = owner.Fullname()
|
||||
}
|
||||
|
||||
d.Pending = append(d.Pending, ud)
|
||||
}
|
||||
|
||||
model = append(model, d)
|
||||
}
|
||||
}
|
||||
|
||||
// Attach numbers to pages, 1.1, 2.1.1 etc.
|
||||
t := []page.Page{}
|
||||
for _, i := range model {
|
||||
t = append(t, i.Page)
|
||||
}
|
||||
page.Numberize(t)
|
||||
for i, j := range t {
|
||||
model[i].Page = j
|
||||
// pending pages get same numbering
|
||||
for k := range model[i].Pending {
|
||||
model[i].Pending[k].Page.Numbering = j.Numbering
|
||||
}
|
||||
}
|
||||
|
||||
// deliver payload
|
||||
response.WriteJSON(w, model)
|
||||
}
|
||||
|
||||
func (h *Handler) workflowPermitsChange(doc dm.Document, ctx domain.RequestContext) (ok bool, err error) {
|
||||
if doc.Protection == workflow.ProtectionNone {
|
||||
if !permission.CanChangeDocument(ctx, *h.Store, doc.RefID) {
|
||||
h.Runtime.Log.Info("attempted forbidden action on document")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// If locked document then no can do
|
||||
if doc.Protection == workflow.ProtectionLock {
|
||||
h.Runtime.Log.Info("attempted action on locked document")
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If approval workflow then only approvers can delete page
|
||||
if doc.Protection == workflow.ProtectionReview {
|
||||
approvers, err := permission.GetDocumentApprovers(ctx, *h.Store, doc.LabelID, doc.RefID)
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error("workflowAllowsChange", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if user.Exists(approvers, ctx.UserID) {
|
||||
h.Runtime.Log.Info("attempted action on document when not approver")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -28,6 +28,10 @@ type Scope struct {
|
|||
Runtime *env.Runtime
|
||||
}
|
||||
|
||||
//**************************************************
|
||||
// Page Revisions
|
||||
//**************************************************
|
||||
|
||||
// Add inserts the given page into the page table, adds that page to the queue of pages to index and audits that the page has been added.
|
||||
func (s Scope) Add(ctx domain.RequestContext, model page.NewPage) (err error) {
|
||||
model.Page.OrgID = ctx.OrgID
|
||||
|
@ -54,8 +58,8 @@ func (s Scope) Add(ctx domain.RequestContext, model page.NewPage) (err error) {
|
|||
model.Page.Sequence = maxSeq * 2
|
||||
}
|
||||
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
model.Page.RefID, model.Page.OrgID, model.Page.DocumentID, model.Page.UserID, model.Page.ContentType, model.Page.PageType, model.Page.Level, model.Page.Title, model.Page.Body, model.Page.Revisions, model.Page.Sequence, model.Page.BlockID, model.Page.Created, model.Page.Revised)
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, status, relativeid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
model.Page.RefID, model.Page.OrgID, model.Page.DocumentID, model.Page.UserID, model.Page.ContentType, model.Page.PageType, model.Page.Level, model.Page.Title, model.Page.Body, model.Page.Revisions, model.Page.Sequence, model.Page.BlockID, model.Page.Status, model.Page.RelativeID, model.Page.Created, model.Page.Revised)
|
||||
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO pagemeta (pageid, orgid, userid, documentid, rawbody, config, externalsource, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
model.Meta.PageID, model.Meta.OrgID, model.Meta.UserID, model.Meta.DocumentID, model.Meta.RawBody, model.Meta.Config, model.Meta.ExternalSource, model.Meta.Created, model.Meta.Revised)
|
||||
|
@ -69,7 +73,7 @@ func (s Scope) Add(ctx domain.RequestContext, model page.NewPage) (err error) {
|
|||
|
||||
// Get returns the pageID page record from the page table.
|
||||
func (s Scope) Get(ctx domain.RequestContext, pageID string) (p page.Page, err error) {
|
||||
err = s.Runtime.Db.Get(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.created, a.revised FROM page a WHERE a.orgid=? AND a.refid=?",
|
||||
err = s.Runtime.Db.Get(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.status, a.relativeid, a.created, a.revised FROM page a WHERE a.orgid=? AND a.refid=?",
|
||||
ctx.OrgID, pageID)
|
||||
|
||||
if err != nil {
|
||||
|
@ -79,9 +83,9 @@ func (s Scope) Get(ctx domain.RequestContext, pageID string) (p page.Page, err e
|
|||
return
|
||||
}
|
||||
|
||||
// GetPages returns a slice containing all the page records for a given documentID, in presentation sequence.
|
||||
// GetPages returns a slice containing all published page records for a given documentID, in presentation sequence.
|
||||
func (s Scope) GetPages(ctx domain.RequestContext, documentID string) (p []page.Page, err error) {
|
||||
err = s.Runtime.Db.Select(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.created, a.revised FROM page a WHERE a.orgid=? AND a.documentid=? ORDER BY a.sequence", ctx.OrgID, documentID)
|
||||
err = s.Runtime.Db.Select(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.status, a.relativeid, a.created, a.revised FROM page a WHERE a.orgid=? AND a.documentid=? AND (a.status=0 OR ((a.status=4 OR a.status=2) AND a.relativeid='')) ORDER BY a.sequence", ctx.OrgID, documentID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute get pages")
|
||||
|
@ -90,10 +94,21 @@ func (s Scope) GetPages(ctx domain.RequestContext, documentID string) (p []page.
|
|||
return
|
||||
}
|
||||
|
||||
// GetUnpublishedPages returns a slice containing all published page records for a given documentID, in presentation sequence.
|
||||
func (s Scope) GetUnpublishedPages(ctx domain.RequestContext, documentID string) (p []page.Page, err error) {
|
||||
err = s.Runtime.Db.Select(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.status, a.relativeid, a.created, a.revised FROM page a WHERE a.orgid=? AND a.documentid=? AND a.status!=0 AND a.relativeid!='' ORDER BY a.sequence", ctx.OrgID, documentID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute get unpublished pages")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPagesWithoutContent returns a slice containing all the page records for a given documentID, in presentation sequence,
|
||||
// but without the body field (which holds the HTML content).
|
||||
func (s Scope) GetPagesWithoutContent(ctx domain.RequestContext, documentID string) (pages []page.Page, err error) {
|
||||
err = s.Runtime.Db.Select(&pages, "SELECT id, refid, orgid, documentid, userid, contenttype, pagetype, sequence, level, title, revisions, blockid, created, revised FROM page WHERE orgid=? AND documentid=? ORDER BY sequence", ctx.OrgID, documentID)
|
||||
err = s.Runtime.Db.Select(&pages, "SELECT id, refid, orgid, documentid, userid, contenttype, pagetype, sequence, level, title, revisions, blockid, status, relativeid, created, revised FROM page WHERE orgid=? AND documentid=? AND status=0 ORDER BY sequence", ctx.OrgID, documentID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select pages for org %s and document %s", ctx.OrgID, documentID))
|
||||
|
@ -119,7 +134,7 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
|
|||
}
|
||||
|
||||
// Update page
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE page SET documentid=:documentid, level=:level, title=:title, body=:body, revisions=:revisions, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid",
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE page SET documentid=:documentid, level=:level, title=:title, body=:body, revisions=:revisions, sequence=:sequence, status=:status, relativeid=:relativeid, revised=:revised WHERE orgid=:orgid AND refid=:refid",
|
||||
&page)
|
||||
|
||||
if err != nil {
|
||||
|
@ -139,6 +154,27 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
|
|||
return
|
||||
}
|
||||
|
||||
// Delete deletes the pageID page in the document.
|
||||
// It then propagates that change into the search table, adds a delete the page revisions history, and audits that the page has been removed.
|
||||
func (s Scope) Delete(ctx domain.RequestContext, documentID, pageID string) (rows int64, err error) {
|
||||
b := mysql.BaseQuery{}
|
||||
rows, err = b.DeleteConstrained(ctx.Transaction, "page", ctx.OrgID, pageID)
|
||||
|
||||
if err == nil {
|
||||
_, _ = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM pagemeta WHERE orgid='%s' AND pageid='%s'", ctx.OrgID, pageID))
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
_, _ = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM useraction WHERE orgid='%s' AND reftypeid='%s' AND reftype='P'", ctx.OrgID, pageID))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//**************************************************
|
||||
// Page Meta
|
||||
//**************************************************
|
||||
|
||||
// UpdateMeta persists meta information associated with a document page.
|
||||
func (s Scope) UpdateMeta(ctx domain.RequestContext, meta page.Meta, updateUserID bool) (err error) {
|
||||
meta.Revised = time.Now().UTC()
|
||||
|
@ -157,43 +193,6 @@ func (s Scope) UpdateMeta(ctx domain.RequestContext, meta page.Meta, updateUserI
|
|||
return
|
||||
}
|
||||
|
||||
// UpdateSequence changes the presentation sequence of the pageID page in the document.
|
||||
// It then propagates that change into the search table and audits that it has occurred.
|
||||
func (s Scope) UpdateSequence(ctx domain.RequestContext, documentID, pageID string, sequence float64) (err error) {
|
||||
_, err = ctx.Transaction.Exec("UPDATE page SET sequence=? WHERE orgid=? AND refid=?", sequence, ctx.OrgID, pageID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute page sequence update")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateLevel changes the heading level of the pageID page in the document.
|
||||
// It then propagates that change into the search table and audits that it has occurred.
|
||||
func (s Scope) UpdateLevel(ctx domain.RequestContext, documentID, pageID string, level int) (err error) {
|
||||
_, err = ctx.Transaction.Exec("UPDATE page SET level=? WHERE orgid=? AND refid=?", level, ctx.OrgID, pageID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute page level update")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes the pageID page in the document.
|
||||
// It then propagates that change into the search table, adds a delete the page revisions history, and audits that the page has been removed.
|
||||
func (s Scope) Delete(ctx domain.RequestContext, documentID, pageID string) (rows int64, err error) {
|
||||
b := mysql.BaseQuery{}
|
||||
rows, err = b.DeleteConstrained(ctx.Transaction, "page", ctx.OrgID, pageID)
|
||||
|
||||
if err == nil {
|
||||
_, _ = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM pagemeta WHERE orgid='%s' AND pageid='%s'", ctx.OrgID, pageID))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPageMeta returns the meta information associated with the page.
|
||||
func (s Scope) GetPageMeta(ctx domain.RequestContext, pageID string) (meta page.Meta, err error) {
|
||||
err = s.Runtime.Db.Get(&meta, "SELECT id, pageid, orgid, userid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND pageid=?",
|
||||
|
@ -222,9 +221,63 @@ func (s Scope) GetDocumentPageMeta(ctx domain.RequestContext, documentID string,
|
|||
return
|
||||
}
|
||||
|
||||
/********************
|
||||
* Page Revisions
|
||||
********************/
|
||||
//**************************************************
|
||||
// Table of contents
|
||||
//**************************************************
|
||||
|
||||
// UpdateSequence changes the presentation sequence of the pageID page in the document.
|
||||
// It then propagates that change into the search table and audits that it has occurred.
|
||||
func (s Scope) UpdateSequence(ctx domain.RequestContext, documentID, pageID string, sequence float64) (err error) {
|
||||
_, err = ctx.Transaction.Exec("UPDATE page SET sequence=? WHERE orgid=? AND refid=?", sequence, ctx.OrgID, pageID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute page sequence update")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateLevel changes the heading level of the pageID page in the document.
|
||||
// It then propagates that change into the search table and audits that it has occurred.
|
||||
func (s Scope) UpdateLevel(ctx domain.RequestContext, documentID, pageID string, level int) (err error) {
|
||||
_, err = ctx.Transaction.Exec("UPDATE page SET level=? WHERE orgid=? AND refid=?", level, ctx.OrgID, pageID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute page level update")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateLevelSequence changes page level and sequence numbers.
|
||||
func (s Scope) UpdateLevelSequence(ctx domain.RequestContext, documentID, pageID string, level int, sequence float64) (err error) {
|
||||
_, err = ctx.Transaction.Exec("UPDATE page SET level=?, sequence=? WHERE orgid=? AND refid=?",
|
||||
level, sequence, ctx.OrgID, pageID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute page level/sequence update")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetNextPageSequence returns the next sequence numbner to use for a page in given document.
|
||||
func (s Scope) GetNextPageSequence(ctx domain.RequestContext, documentID string) (maxSeq float64, err error) {
|
||||
row := s.Runtime.Db.QueryRow("SELECT max(sequence) FROM page WHERE orgid=? AND documentid=?", ctx.OrgID, documentID)
|
||||
|
||||
err = row.Scan(&maxSeq)
|
||||
if err != nil {
|
||||
maxSeq = 2048
|
||||
}
|
||||
|
||||
maxSeq = maxSeq * 2
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//**************************************************
|
||||
// Page Revisions
|
||||
//**************************************************
|
||||
|
||||
// GetPageRevision returns the revisionID page revision record.
|
||||
func (s Scope) GetPageRevision(ctx domain.RequestContext, revisionID string) (revision page.Revision, err error) {
|
||||
|
@ -273,17 +326,3 @@ func (s Scope) DeletePageRevisions(ctx domain.RequestContext, pageID string) (ro
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// GetNextPageSequence returns the next sequence numbner to use for a page in given document.
|
||||
func (s Scope) GetNextPageSequence(ctx domain.RequestContext, documentID string) (maxSeq float64, err error) {
|
||||
row := s.Runtime.Db.QueryRow("SELECT max(sequence) FROM page WHERE orgid=? AND documentid=?", ctx.OrgID, documentID)
|
||||
|
||||
err = row.Scan(&maxSeq)
|
||||
if err != nil {
|
||||
maxSeq = 2048
|
||||
}
|
||||
|
||||
maxSeq = maxSeq * 2
|
||||
|
||||
return
|
||||
}
|
||||
|
|
52
domain/page/page.go
Normal file
52
domain/page/page.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
// 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
|
||||
|
||||
package page
|
||||
|
||||
import (
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/model/page"
|
||||
)
|
||||
|
||||
// LevelizeDocument generates level and sequence numbers for all document sections
|
||||
func (h *Handler) LevelizeDocument(ctx domain.RequestContext, documentID string) {
|
||||
method := "page.Levelize"
|
||||
var err error
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
// Re-level all pages in document
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
p2, err := h.Store.Page.GetPages(ctx, documentID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
page.Levelize(p2)
|
||||
page.Sequenize(p2)
|
||||
|
||||
for _, i := range p2 {
|
||||
err = h.Store.Page.UpdateLevelSequence(ctx, documentID, i.RefID, int(i.Level), i.Sequence)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
|
@ -69,7 +69,7 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
var model = permission.PermissionsModel{}
|
||||
var model = permission.SpaceRequestModel{}
|
||||
err = json.Unmarshal(body, &model)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -398,3 +398,192 @@ func (h *Handler) SetCategoryPermissions(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// GetDocumentPermissions returns permissions for all users for given document.
|
||||
func (h *Handler) GetDocumentPermissions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "space.GetDocumentPermissions"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
perms, err := h.Store.Permission.GetDocumentPermissions(ctx, documentID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(perms) == 0 {
|
||||
perms = []permission.Permission{}
|
||||
}
|
||||
|
||||
userPerms := make(map[string][]permission.Permission)
|
||||
for _, p := range perms {
|
||||
userPerms[p.WhoID] = append(userPerms[p.WhoID], p)
|
||||
}
|
||||
|
||||
records := []permission.DocumentRecord{}
|
||||
for _, up := range userPerms {
|
||||
records = append(records, permission.DecodeUserDocumentPermissions(up))
|
||||
}
|
||||
|
||||
response.WriteJSON(w, records)
|
||||
}
|
||||
|
||||
// GetUserDocumentPermissions returns permissions for the requested document, for current user.
|
||||
func (h *Handler) GetUserDocumentPermissions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "space.GetUserDocumentPermissions"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
perms, err := h.Store.Permission.GetUserDocumentPermissions(ctx, documentID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(perms) == 0 {
|
||||
perms = []permission.Permission{}
|
||||
}
|
||||
|
||||
record := permission.DecodeUserDocumentPermissions(perms)
|
||||
response.WriteJSON(w, record)
|
||||
}
|
||||
|
||||
// SetDocumentPermissions persists specified document permissions
|
||||
// These permissions override document permissions
|
||||
func (h *Handler) SetDocumentPermissions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "space.SetDocumentPermissions"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
id := request.Param(r, "documentID")
|
||||
if len(id) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, id)
|
||||
if err != nil {
|
||||
response.WriteNotFoundError(w, method, "document not found")
|
||||
return
|
||||
}
|
||||
|
||||
sp, err := h.Store.Space.Get(ctx, doc.LabelID)
|
||||
if err != nil {
|
||||
response.WriteNotFoundError(w, method, "space not found")
|
||||
return
|
||||
}
|
||||
|
||||
// if !HasPermission(ctx, *h.Store, doc.LabelID, permission.SpaceManage, permission.SpaceOwner) {
|
||||
// response.WriteForbiddenError(w)
|
||||
// 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.DocumentRecord{}
|
||||
err = json.Unmarshal(body, &model)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// We compare new permisions to what we had before.
|
||||
// Why? So we can send out space invitation emails.
|
||||
previousRoles, err := h.Store.Permission.GetDocumentPermissions(ctx, id)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Store all previous approval roles as map for easy querying
|
||||
previousRoleUsers := make(map[string]bool)
|
||||
for _, v := range previousRoles {
|
||||
if v.Action == permission.DocumentApprove {
|
||||
previousRoleUsers[v.WhoID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Get user who is setting document permissions so we can send out emails with context
|
||||
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke all previous permissions for this document
|
||||
_, err = h.Store.Permission.DeleteDocumentPermissions(ctx, id)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s",
|
||||
sp.RefID, stringutil.MakeSlug(sp.Name), doc.RefID, stringutil.MakeSlug(doc.Title)))
|
||||
|
||||
for _, perm := range model {
|
||||
perm.OrgID = ctx.OrgID
|
||||
perm.DocumentID = id
|
||||
|
||||
// Only persist if there is a role!
|
||||
if permission.HasAnyDocumentPermission(perm) {
|
||||
r := permission.EncodeUserDocumentPermissions(perm)
|
||||
|
||||
for _, p := range r {
|
||||
err = h.Store.Permission.AddPermission(ctx, p)
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error("set document permission", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send email notification to users who have been given document approver role
|
||||
if _, isExisting := previousRoleUsers[perm.UserID]; !isExisting {
|
||||
|
||||
// we skip 'everyone' (user id != empty string)
|
||||
if perm.UserID != "0" && perm.UserID != "" && perm.DocumentRoleApprove {
|
||||
existingUser, err := h.Store.User.Get(ctx, perm.UserID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
break
|
||||
}
|
||||
|
||||
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
|
||||
go mailer.DocumentApprover(existingUser.Email, inviter.Fullname(), url, doc.Title)
|
||||
h.Runtime.Log.Info(fmt.Sprintf("%s has made %s document approver for: %s", inviter.Email, existingUser.Email, doc.Title))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentPermission)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ func (s Scope) GetUserSpacePermissions(ctx domain.RequestContext, spaceID string
|
|||
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 refid=?
|
||||
AND p.who='role' AND (r.userid=? OR r.userid='0')`,
|
||||
ctx.OrgID, spaceID, ctx.UserID, ctx.OrgID, spaceID, ctx.OrgID)
|
||||
ctx.OrgID, spaceID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
|
@ -200,7 +200,7 @@ func (s Scope) GetUserCategoryPermissions(ctx domain.RequestContext, userID stri
|
|||
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='category' AND p.who='role'`,
|
||||
WHERE p.orgid=? AND p.location='category' AND p.who='role'`,
|
||||
ctx.OrgID, userID, ctx.OrgID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
|
@ -212,3 +212,55 @@ func (s Scope) GetUserCategoryPermissions(ctx domain.RequestContext, userID stri
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserDocumentPermissions returns document permissions for user.
|
||||
// Context is used to for user ID.
|
||||
func (s Scope) GetUserDocumentPermissions(ctx domain.RequestContext, documentID 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='document' AND refid=? AND who='user' AND (whoid=? OR whoid='0')
|
||||
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='document' AND refid=?
|
||||
AND p.who='role' AND (r.userid=? OR r.userid='0')`,
|
||||
ctx.OrgID, documentID, ctx.UserID, ctx.OrgID, documentID, ctx.OrgID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select user document permissions %s", ctx.UserID))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetDocumentPermissions returns documents permissions for all users.
|
||||
func (s Scope) GetDocumentPermissions(ctx domain.RequestContext, documentID 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='document' 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='document' AND p.refid=?
|
||||
AND p.who='role'`,
|
||||
ctx.OrgID, documentID, ctx.OrgID, documentID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select document permissions %s", ctx.UserID))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteDocumentPermissions removes records from permissions table for given document.
|
||||
func (s Scope) DeleteDocumentPermissions(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||
b := mysql.BaseQuery{}
|
||||
|
||||
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='document' AND refid='%s'", ctx.OrgID, documentID)
|
||||
|
||||
return b.DeleteWhere(ctx.Transaction, sql)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
"github.com/documize/community/domain"
|
||||
pm "github.com/documize/community/model/permission"
|
||||
u "github.com/documize/community/model/user"
|
||||
)
|
||||
|
||||
// CanViewSpaceDocument returns if the user has permission to view a document within the specified folder.
|
||||
|
@ -153,7 +154,6 @@ func CanViewSpace(ctx domain.RequestContext, s domain.Store, spaceID string) boo
|
|||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
|
||||
pm.ContainsPermission(role.Action, pm.SpaceView, pm.SpaceManage, pm.SpaceOwner) {
|
||||
|
@ -187,3 +187,40 @@ func HasPermission(ctx domain.RequestContext, s domain.Store, spaceID string, ac
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetDocumentApprovers returns list of users who can approve given document in given space
|
||||
func GetDocumentApprovers(ctx domain.RequestContext, s domain.Store, spaceID, documentID string) (users []u.User, err error) {
|
||||
users = []u.User{}
|
||||
prev := make(map[string]bool) // used to ensure we only process user once
|
||||
|
||||
// check space permissions
|
||||
sp, err := s.Permission.GetSpacePermissions(ctx, spaceID)
|
||||
for _, p := range sp {
|
||||
if p.Action == pm.DocumentApprove {
|
||||
user, err := s.User.Get(ctx, p.WhoID)
|
||||
if err == nil {
|
||||
prev[user.RefID] = true
|
||||
users = append(users, user)
|
||||
} else {
|
||||
return users, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check document permissions
|
||||
dp, err := s.Permission.GetDocumentPermissions(ctx, documentID)
|
||||
for _, p := range dp {
|
||||
if p.Action == pm.DocumentApprove {
|
||||
user, err := s.User.Get(ctx, p.WhoID)
|
||||
if err == nil {
|
||||
if _, isExisting := prev[user.RefID]; !isExisting {
|
||||
users = append(users, user)
|
||||
}
|
||||
} else {
|
||||
return users, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/page"
|
||||
"github.com/documize/community/model/search"
|
||||
"github.com/documize/community/model/workflow"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -93,6 +94,11 @@ func (s Scope) DeleteDocument(ctx domain.RequestContext, ID string) (err error)
|
|||
// IndexContent adds search index entry for document context.
|
||||
// Any existing document entries are removed.
|
||||
func (s Scope) IndexContent(ctx domain.RequestContext, p page.Page) (err error) {
|
||||
// we do not index pending pages
|
||||
if p.Status == workflow.ChangePending || p.Status == workflow.ChangePendingNew {
|
||||
return
|
||||
}
|
||||
|
||||
// remove previous search entries
|
||||
_, err = ctx.Transaction.Exec("DELETE FROM search WHERE orgid=? AND documentid=? AND itemid=? AND itemtype='page'",
|
||||
ctx.OrgID, p.DocumentID, p.RefID)
|
||||
|
@ -216,7 +222,7 @@ func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType strin
|
|||
(
|
||||
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'
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
))
|
||||
|
@ -273,9 +279,10 @@ func (s Scope) matchLike(ctx domain.RequestContext, keywords, itemType string) (
|
|||
(
|
||||
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'
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') 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=?
|
||||
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=? OR r.userid='0')
|
||||
))
|
||||
)
|
||||
AND s.content LIKE ?`
|
||||
|
|
|
@ -118,7 +118,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceManage, permission.SpaceView,
|
||||
permission.DocumentAdd, permission.DocumentCopy, permission.DocumentDelete, permission.DocumentEdit, permission.DocumentMove,
|
||||
permission.DocumentTemplate)
|
||||
permission.DocumentTemplate, permission.DocumentApprove)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -642,7 +642,7 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
u, err := h.Store.User.GetBySerial(ctx, model.Serial)
|
||||
if err != nil && err == sql.ErrNoRows {
|
||||
response.WriteDuplicateError(w, method, "user")
|
||||
response.WriteNotFoundError(w, method, "user")
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
@ -650,6 +650,7 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
|
|||
// AcceptShare does not authenticate the user hence the context needs to set up
|
||||
ctx.UserID = u.RefID
|
||||
|
||||
// Prepare user data
|
||||
u.Firstname = model.Firstname
|
||||
u.Lastname = model.Lastname
|
||||
u.Initials = stringutil.MakeInitials(u.Firstname, u.Lastname)
|
||||
|
@ -670,7 +671,6 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
salt := secrets.GenerateSalt()
|
||||
|
||||
err = h.Store.User.UpdateUserPassword(ctx, u.RefID, salt, secrets.GeneratePassword(model.Password, salt))
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
|
@ -679,10 +679,12 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSpaceJoin)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSpaceJoin)
|
||||
|
||||
// We send back POJO and not fully authenticated user object as
|
||||
// SSO should take place thereafter
|
||||
response.WriteJSON(w, u)
|
||||
}
|
||||
|
||||
|
@ -704,10 +706,10 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if sp.UserID != ctx.UserID {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
// if sp.UserID != ctx.UserID {
|
||||
// response.WriteForbiddenError(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
|
|
@ -94,6 +94,9 @@ type PermissionStorer interface {
|
|||
GetCategoryPermissions(ctx RequestContext, catID string) (r []permission.Permission, err error)
|
||||
GetCategoryUsers(ctx RequestContext, catID string) (u []user.User, err error)
|
||||
GetUserCategoryPermissions(ctx RequestContext, userID string) (r []permission.Permission, err error)
|
||||
GetUserDocumentPermissions(ctx RequestContext, documentID string) (r []permission.Permission, err error)
|
||||
GetDocumentPermissions(ctx RequestContext, documentID string) (r []permission.Permission, err error)
|
||||
DeleteDocumentPermissions(ctx RequestContext, documentID string) (rows int64, err error)
|
||||
}
|
||||
|
||||
// UserStorer defines required methods for user management
|
||||
|
@ -245,17 +248,19 @@ type PageStorer interface {
|
|||
Add(ctx RequestContext, model page.NewPage) (err error)
|
||||
Get(ctx RequestContext, pageID string) (p page.Page, err error)
|
||||
GetPages(ctx RequestContext, documentID string) (p []page.Page, err error)
|
||||
GetUnpublishedPages(ctx RequestContext, documentID string) (p []page.Page, err error)
|
||||
GetPagesWithoutContent(ctx RequestContext, documentID string) (pages []page.Page, err error)
|
||||
Update(ctx RequestContext, page page.Page, refID, userID string, skipRevision bool) (err error)
|
||||
Delete(ctx RequestContext, documentID, pageID string) (rows int64, err error)
|
||||
GetPageMeta(ctx RequestContext, pageID string) (meta page.Meta, err error)
|
||||
GetDocumentPageMeta(ctx RequestContext, documentID string, externalSourceOnly bool) (meta []page.Meta, err error)
|
||||
UpdateMeta(ctx RequestContext, meta page.Meta, updateUserID bool) (err error)
|
||||
UpdateSequence(ctx RequestContext, documentID, pageID string, sequence float64) (err error)
|
||||
UpdateLevel(ctx RequestContext, documentID, pageID string, level int) (err error)
|
||||
Delete(ctx RequestContext, documentID, pageID string) (rows int64, err error)
|
||||
GetPageMeta(ctx RequestContext, pageID string) (meta page.Meta, err error)
|
||||
UpdateLevelSequence(ctx RequestContext, documentID, pageID string, level int, sequence float64) (err error)
|
||||
GetNextPageSequence(ctx RequestContext, documentID string) (maxSeq float64, err error)
|
||||
GetPageRevision(ctx RequestContext, revisionID string) (revision page.Revision, err error)
|
||||
GetPageRevisions(ctx RequestContext, pageID string) (revisions []page.Revision, err error)
|
||||
GetDocumentRevisions(ctx RequestContext, documentID string) (revisions []page.Revision, err error)
|
||||
GetDocumentPageMeta(ctx RequestContext, documentID string, externalSourceOnly bool) (meta []page.Meta, err error)
|
||||
DeletePageRevisions(ctx RequestContext, pageID string) (rows int64, err error)
|
||||
GetNextPageSequence(ctx RequestContext, documentID string) (maxSeq float64, err error)
|
||||
}
|
||||
|
|
|
@ -502,7 +502,7 @@ func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
|||
newPassword := string(body)
|
||||
|
||||
// can only update your own account unless you are an admin
|
||||
if !ctx.Administrator || (!ctx.Administrator && userID != ctx.UserID) {
|
||||
if !ctx.Administrator && userID != ctx.UserID {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ func (s Scope) GetByToken(ctx domain.RequestContext, token string) (u user.User,
|
|||
// This occurs when we you share a folder with a new user and they have to complete
|
||||
// the onboarding process.
|
||||
func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.User, err error) {
|
||||
err = s.Runtime.Db.Get("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE salt=?", serial)
|
||||
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE salt=?", serial)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("execute user select by serial %s", serial))
|
||||
|
|
|
@ -33,7 +33,7 @@ func main() {
|
|||
rt := env.Runtime{}
|
||||
|
||||
// wire up logging implementation
|
||||
rt.Log = logging.NewLogger()
|
||||
rt.Log = logging.NewLogger(false)
|
||||
|
||||
// wire up embedded web assets handler
|
||||
web.Embed = embed.NewEmbedder()
|
||||
|
@ -41,7 +41,7 @@ func main() {
|
|||
// product details
|
||||
rt.Product = env.ProdInfo{}
|
||||
rt.Product.Major = "1"
|
||||
rt.Product.Minor = "55"
|
||||
rt.Product.Minor = "56"
|
||||
rt.Product.Patch = "0"
|
||||
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
||||
rt.Product.Edition = "Community"
|
||||
|
|
|
@ -25,8 +25,9 @@ import (
|
|||
|
||||
// Logger is how we log.
|
||||
type Logger struct {
|
||||
db *sqlx.DB
|
||||
log *log.Logger
|
||||
db *sqlx.DB
|
||||
log *log.Logger
|
||||
trace bool // shows Info() entries
|
||||
}
|
||||
|
||||
// Info logs message.
|
||||
|
@ -34,6 +35,13 @@ func (l Logger) Info(message string) {
|
|||
l.log.Println(message)
|
||||
}
|
||||
|
||||
// Trace logs message if tracing enabled.
|
||||
func (l Logger) Trace(message string) {
|
||||
if l.trace {
|
||||
l.log.Println(message)
|
||||
}
|
||||
}
|
||||
|
||||
// Error logs error with message.
|
||||
func (l Logger) Error(message string, err error) {
|
||||
l.log.Println(message)
|
||||
|
@ -56,13 +64,14 @@ func (l Logger) SetDB(logger env.Logger, db *sqlx.DB) env.Logger {
|
|||
}
|
||||
|
||||
// NewLogger returns initialized logging instance.
|
||||
func NewLogger() env.Logger {
|
||||
func NewLogger(trace bool) env.Logger {
|
||||
l := log.New(os.Stdout, "", 0)
|
||||
l.SetOutput(os.Stdout)
|
||||
// log.SetOutput(os.Stdout)
|
||||
|
||||
var logger Logger
|
||||
logger.log = l
|
||||
logger.trace = trace
|
||||
|
||||
return logger
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4,17 +4,46 @@ module.exports = {
|
|||
ecmaVersion: 2017,
|
||||
sourceType: 'module'
|
||||
},
|
||||
extends: 'eslint:recommended',
|
||||
plugins: [
|
||||
'ember'
|
||||
],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:ember/recommended'
|
||||
],
|
||||
env: {
|
||||
browser: true,
|
||||
jquery: true,
|
||||
qunit: true,
|
||||
embertest: true
|
||||
browser: true
|
||||
},
|
||||
rules: {
|
||||
},
|
||||
overrides: [
|
||||
// node files
|
||||
{
|
||||
files: [
|
||||
'testem.js',
|
||||
'ember-cli-build.js',
|
||||
'config/**/*.js'
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
ecmaVersion: 2015
|
||||
},
|
||||
env: {
|
||||
browser: false,
|
||||
node: true
|
||||
}
|
||||
},
|
||||
|
||||
// test files
|
||||
{
|
||||
files: ['tests/**/*.js'],
|
||||
excludedFiles: ['tests/dummy/**/*.js'],
|
||||
env: {
|
||||
embertest: true
|
||||
}
|
||||
}
|
||||
],
|
||||
globals: {
|
||||
"$": true,
|
||||
"is": true,
|
||||
"_": true,
|
||||
"tinymce": true,
|
||||
|
@ -31,5 +60,5 @@ module.exports = {
|
|||
"Keycloak": true,
|
||||
"slug": true,
|
||||
"interact": true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,18 +4,26 @@ node_js:
|
|||
- "6"
|
||||
|
||||
sudo: false
|
||||
dist: trusty
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.npm
|
||||
yarn: true
|
||||
|
||||
env:
|
||||
global:
|
||||
# See https://git.io/vdao3 for details.
|
||||
- JOBS=1
|
||||
|
||||
before_install:
|
||||
- npm config set spin false
|
||||
- npm install -g phantomjs-prebuilt
|
||||
- phantomjs --version
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
|
||||
install:
|
||||
- npm install
|
||||
- yarn install --non-interactive
|
||||
|
||||
script:
|
||||
- npm test
|
||||
- yarn lint:js
|
||||
- yarn test
|
||||
|
|
|
@ -27,16 +27,20 @@ export default Component.extend({
|
|||
KeycloakPublicKeyError: empty('keycloakConfig.publicKey'),
|
||||
KeycloakAdminUserError: empty('keycloakConfig.adminUser'),
|
||||
KeycloakAdminPasswordError: empty('keycloakConfig.adminPassword'),
|
||||
keycloakConfig: {
|
||||
url: '',
|
||||
realm: '',
|
||||
clientId: '',
|
||||
publicKey: '',
|
||||
adminUser: '',
|
||||
adminPassword: '',
|
||||
group: '',
|
||||
disableLogout: false,
|
||||
defaultPermissionAddSpace: false
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.keycloakConfig = {
|
||||
url: '',
|
||||
realm: '',
|
||||
clientId: '',
|
||||
publicKey: '',
|
||||
adminUser: '',
|
||||
adminPassword: '',
|
||||
group: '',
|
||||
disableLogout: false,
|
||||
defaultPermissionAddSpace: false
|
||||
};
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
|
@ -139,7 +143,7 @@ export default Component.extend({
|
|||
});
|
||||
} else {
|
||||
if (data.authProvider === this.get('appMeta.authProvider')) {
|
||||
// this.showNotification(response.message);
|
||||
this.showNotification(response.message);
|
||||
} else {
|
||||
this.get('onChange')(data);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty } from '@ember/object/computed';
|
||||
import Component from '@ember/component';
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import Component from '@ember/component';
|
||||
import { schedule, debounce } from '@ember/runloop';
|
||||
import AuthProvider from '../../mixins/auth';
|
||||
|
@ -17,13 +18,17 @@ import ModalMixin from '../../mixins/modal';
|
|||
export default Component.extend(AuthProvider, ModalMixin, {
|
||||
editUser: null,
|
||||
deleteUser: null,
|
||||
password: {},
|
||||
filter: '',
|
||||
filteredUsers: [],
|
||||
selectedUsers: [],
|
||||
hasSelectedUsers: false,
|
||||
showDeleteDialog: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.password = {};
|
||||
this.filteredUsers = [];
|
||||
this.selectedUsers = [];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
|
@ -74,25 +79,29 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
|||
toggleActive(id) {
|
||||
let user = this.users.findBy("id", id);
|
||||
user.set('active', !user.get('active'));
|
||||
this.attrs.onSave(user);
|
||||
let cb = this.get('onSave');
|
||||
cb(user);
|
||||
},
|
||||
|
||||
toggleEditor(id) {
|
||||
let user = this.users.findBy("id", id);
|
||||
user.set('editor', !user.get('editor'));
|
||||
this.attrs.onSave(user);
|
||||
let cb = this.get('onSave');
|
||||
cb(user);
|
||||
},
|
||||
|
||||
toggleAdmin(id) {
|
||||
let user = this.users.findBy("id", id);
|
||||
user.set('admin', !user.get('admin'));
|
||||
this.attrs.onSave(user);
|
||||
let cb = this.get('onSave');
|
||||
cb(user);
|
||||
},
|
||||
|
||||
toggleUsers(id) {
|
||||
let user = this.users.findBy("id", id);
|
||||
user.set('viewUsers', !user.get('viewUsers'));
|
||||
this.attrs.onSave(user);
|
||||
let cb = this.get('onSave');
|
||||
cb(user);
|
||||
},
|
||||
|
||||
onShowEdit(id) {
|
||||
|
@ -135,11 +144,14 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
|||
$('#edit-user-modal').modal('hide');
|
||||
$('#edit-user-modal').modal('dispose');
|
||||
|
||||
this.attrs.onSave(user);
|
||||
let cb = this.get('onSave');
|
||||
cb(user);
|
||||
|
||||
if (is.not.empty(password.password) && is.not.empty(password.confirmation) &&
|
||||
password.password === password.confirmation) {
|
||||
this.attrs.onPassword(user, password.password);
|
||||
|
||||
let pw = this.get('onPassword');
|
||||
pw(user, password.password);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -153,7 +165,9 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
|||
|
||||
this.set('selectedUsers', []);
|
||||
this.set('hasSelectedUsers', false);
|
||||
this.attrs.onDelete(this.get('deleteUser.id'));
|
||||
|
||||
let cb = this.get('onDelete');
|
||||
cb(this.get('deleteUser.id'));
|
||||
|
||||
return true;
|
||||
},
|
||||
|
@ -162,7 +176,8 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
|||
let su = this.get('selectedUsers');
|
||||
|
||||
su.forEach(userId => {
|
||||
this.attrs.onDelete(userId);
|
||||
let cb = this.get('onDelete');
|
||||
cb(userId);
|
||||
});
|
||||
|
||||
this.set('selectedUsers', []);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty, and } from '@ember/object/computed';
|
||||
import Component from '@ember/component';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
@ -16,7 +17,6 @@ import { get, set } from '@ember/object';
|
|||
import AuthProvider from '../../mixins/auth';
|
||||
|
||||
export default Component.extend(AuthProvider, {
|
||||
newUser: { firstname: "", lastname: "", email: "", active: true },
|
||||
firstnameEmpty: empty('newUser.firstname'),
|
||||
lastnameEmpty: empty('newUser.lastname'),
|
||||
emailEmpty: empty('newUser.email'),
|
||||
|
@ -24,6 +24,11 @@ export default Component.extend(AuthProvider, {
|
|||
hasLastnameEmptyError: and('lastnameEmpty', 'lastnameError'),
|
||||
hasEmailEmptyError: and('emailEmpty', 'emailError'),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.newUser = { firstname: "", lastname: "", email: "", active: true };
|
||||
},
|
||||
|
||||
actions: {
|
||||
add() {
|
||||
if (isEmpty(this.get('newUser.firstname'))) {
|
||||
|
|
|
@ -44,11 +44,13 @@ export default Component.extend({
|
|||
|
||||
actions: {
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(page, meta) {
|
||||
this.attrs.onAction(page, meta);
|
||||
let cb = this.get('onAction');
|
||||
cb(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -20,25 +20,17 @@ export default Component.extend(ModalMixin, TooltipMixin, {
|
|||
link: service(),
|
||||
linkName: '',
|
||||
selection: null,
|
||||
|
||||
tab1Selected: true,
|
||||
tab2Selected: false,
|
||||
tab3Selected: false,
|
||||
showSections: computed('tab1Selected', function() { return this.get('tab1Selected'); }),
|
||||
showAttachments: computed('tab2Selected', function() { return this.get('tab2Selected'); }),
|
||||
showSearch: computed('tab3Selected', function() { return this.get('tab3Selected'); }),
|
||||
|
||||
keywords: '',
|
||||
matches: {
|
||||
documents: [],
|
||||
pages: [],
|
||||
attachments: []
|
||||
},
|
||||
hasMatches: computed('matches', function () {
|
||||
let m = this.get('matches');
|
||||
return m.documents.length || m.pages.length || m.attachments.length;
|
||||
}),
|
||||
|
||||
modalId: computed('page', function() { return '#content-linker-modal-' + this.get('page.id'); }),
|
||||
showModal: false,
|
||||
onToggle: function() {
|
||||
|
@ -63,17 +55,23 @@ export default Component.extend(ModalMixin, TooltipMixin, {
|
|||
this.modalOpen(modalId, {show: true});
|
||||
}.observes('showModal'),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.matches = {
|
||||
documents: [],
|
||||
pages: [],
|
||||
attachments: []
|
||||
};
|
||||
},
|
||||
|
||||
didRender() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.renderTooltips();
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.removeTooltips();
|
||||
|
||||
this.modalClose(this.get('modalId'));
|
||||
},
|
||||
|
||||
|
|
|
@ -18,11 +18,13 @@ export default Component.extend({
|
|||
|
||||
actions: {
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(page, meta) {
|
||||
this.attrs.onAction(page, meta);
|
||||
let cb = this.get('onAction');
|
||||
cb(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -9,7 +9,9 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty } from '@ember/object/computed';
|
||||
import { computed } from '@ember/object';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
|
@ -22,6 +24,19 @@ export default Component.extend({
|
|||
hasNameError: empty('docName'),
|
||||
hasExcerptError: empty('docExcerpt'),
|
||||
|
||||
canEdit: computed('permssions', 'document', function() {
|
||||
let constants = this.get('constants');
|
||||
let permissions = this.get('permissions');
|
||||
|
||||
if (permissions.get('documentEdit') && this.get('document.protection') === constants.ProtectionType.None) {
|
||||
return true;
|
||||
} else if (permissions.get('documentApprove') && this.get('document.protection') === constants.ProtectionType.Review) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}),
|
||||
|
||||
keyUp(e) {
|
||||
if (e.keyCode === 27) { // escape key
|
||||
this.send('onCancel');
|
||||
|
@ -48,7 +63,8 @@ export default Component.extend({
|
|||
this.set('document.excerpt', this.get('docExcerpt'));
|
||||
this.set('editMode', false);
|
||||
|
||||
this.attrs.onSaveDocument(this.get('document'));
|
||||
let cb = this.get('onSaveDocument');
|
||||
cb(this.get('document'));
|
||||
},
|
||||
|
||||
onCancel() {
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { computed } from '@ember/object';
|
||||
import { notEmpty } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
import { A } from "@ember/array"
|
||||
|
@ -19,12 +21,9 @@ export default Component.extend({
|
|||
documentService: service('document'),
|
||||
categoryService: service('category'),
|
||||
sessionService: service('session'),
|
||||
maxTags: 3,
|
||||
|
||||
categories: A([]),
|
||||
newCategory: '',
|
||||
tagz: A([]),
|
||||
tagzModal: A([]),
|
||||
newTag: '',
|
||||
showCategoryModal: false,
|
||||
hasCategories: computed('categories', function() {
|
||||
return this.get('categories').length > 0;
|
||||
|
@ -36,9 +35,63 @@ export default Component.extend({
|
|||
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
|
||||
}),
|
||||
|
||||
maxTags: 3,
|
||||
tagz: A([]),
|
||||
tagzModal: A([]),
|
||||
newTag: '',
|
||||
|
||||
contributorMsg: '',
|
||||
approverMsg: '',
|
||||
userChanges: notEmpty('contributorMsg'),
|
||||
isApprover: computed('permissions', function() {
|
||||
return this.get('permissions.documentApprove');
|
||||
}),
|
||||
changeControlMsg: computed('document.protection', function() {
|
||||
let p = this.get('document.protection');
|
||||
let constants = this.get('constants');
|
||||
let msg = '';
|
||||
|
||||
switch (p) {
|
||||
case constants.ProtectionType.None:
|
||||
msg = constants.ProtectionType.NoneLabel;
|
||||
break;
|
||||
case constants.ProtectionType.Lock:
|
||||
msg = constants.ProtectionType.LockLabel;
|
||||
break;
|
||||
case constants.ProtectionType.Review:
|
||||
msg = constants.ProtectionType.ReviewLabel;
|
||||
break;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}),
|
||||
approvalMsg: computed('document.{protection,approval}', function() {
|
||||
let p = this.get('document.protection');
|
||||
let a = this.get('document.approval');
|
||||
let constants = this.get('constants');
|
||||
let msg = '';
|
||||
|
||||
if (p === constants.ProtectionType.Review) {
|
||||
switch (a) {
|
||||
case constants.ApprovalType.Anybody:
|
||||
msg = constants.ApprovalType.AnybodyLabel;
|
||||
break;
|
||||
case constants.ApprovalType.Majority:
|
||||
msg = constants.ApprovalType.MajorityLabel;
|
||||
break;
|
||||
case constants.ApprovalType.Unanimous:
|
||||
msg = constants.ApprovalType.UnanimousLabel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return msg;
|
||||
}),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.load();
|
||||
this.workflowStatus();
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
|
@ -100,6 +153,39 @@ export default Component.extend({
|
|||
this.set('tagz', A(tagz));
|
||||
},
|
||||
|
||||
workflowStatus() {
|
||||
let pages = this.get('pages');
|
||||
let contributorMsg = '';
|
||||
let userPendingCount = 0;
|
||||
let userReviewCount = 0;
|
||||
let userRejectedCount = 0;
|
||||
let approverMsg = '';
|
||||
let approverPendingCount = 0;
|
||||
let approverReviewCount = 0;
|
||||
let approverRejectedCount = 0;
|
||||
|
||||
pages.forEach((item) => {
|
||||
if (item.get('userHasChangePending')) userPendingCount+=1;
|
||||
if (item.get('userHasChangeAwaitingReview')) userReviewCount+=1;
|
||||
if (item.get('userHasChangeRejected')) userRejectedCount+=1;
|
||||
if (item.get('changePending')) approverPendingCount+=1;
|
||||
if (item.get('changeAwaitingReview')) approverReviewCount+=1;
|
||||
if (item.get('changeRejected')) approverRejectedCount+=1;
|
||||
});
|
||||
|
||||
if (userPendingCount > 0 || userReviewCount > 0 || userRejectedCount > 0) {
|
||||
let label = userPendingCount === 1 ? 'change' : 'changes';
|
||||
contributorMsg = `${userPendingCount} ${label} progressing, ${userReviewCount} awaiting review, ${userRejectedCount} rejected`;
|
||||
}
|
||||
this.set('contributorMsg', contributorMsg);
|
||||
|
||||
if (approverPendingCount > 0 || approverReviewCount > 0 || approverRejectedCount > 0) {
|
||||
let label = approverPendingCount === 1 ? 'change' : 'changes';
|
||||
approverMsg = `${approverPendingCount} ${label} progressing, ${approverReviewCount} awaiting review, ${approverRejectedCount} rejected`;
|
||||
}
|
||||
this.set('approverMsg', approverMsg);
|
||||
},
|
||||
|
||||
actions: {
|
||||
onShowCategoryModal() {
|
||||
this.set('showCategoryModal', true);
|
||||
|
@ -179,13 +265,15 @@ export default Component.extend({
|
|||
|
||||
let doc = this.get('document');
|
||||
doc.set('tags', save);
|
||||
this.attrs.onSaveDocument(doc);
|
||||
|
||||
let cb = this.get('onSaveDocument');
|
||||
cb(doc);
|
||||
|
||||
this.load();
|
||||
this.set('newTag', '');
|
||||
|
||||
$('#document-tags-modal').modal('hide');
|
||||
$('#document-tags-modal').modal('dispose');
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -10,55 +10,63 @@
|
|||
// https://documize.com
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import Component from '@ember/component';
|
||||
import NotifierMixin from '../../mixins/notifier';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
|
||||
export default Component.extend(NotifierMixin, TooltipMixin, {
|
||||
export default Component.extend(TooltipMixin, {
|
||||
documentService: service('document'),
|
||||
sectionService: service('section'),
|
||||
editMode: false,
|
||||
editPage: null,
|
||||
editMeta: null,
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
if (this.get('isDestroyed') || this.get('isDestroying')) return;
|
||||
|
||||
if (this.get('isDestroyed') || this.get('isDestroying')) {
|
||||
return;
|
||||
if (this.get('session.authenticated')) {
|
||||
this.workflow();
|
||||
}
|
||||
|
||||
let page = this.get('page');
|
||||
if (this.get('toEdit') === this.get('page.id') && this.get('permissions.documentEdit')) this.send('onEdit');
|
||||
},
|
||||
|
||||
this.get('documentService').getPageMeta(page.get('documentId'), page.get('id')).then((meta) => {
|
||||
if (this.get('isDestroyed') || this.get('isDestroying')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('meta', meta);
|
||||
if (this.get('toEdit') === this.get('page.id') && this.get('permissions.documentEdit')) {
|
||||
this.send('onEdit');
|
||||
}
|
||||
});
|
||||
workflow() {
|
||||
this.set('editPage', this.get('page'));
|
||||
this.set('editMeta', this.get('meta'));
|
||||
},
|
||||
|
||||
actions: {
|
||||
onSavePage(page, meta) {
|
||||
this.set('page', page);
|
||||
this.set('meta', meta);
|
||||
let constants = this.get('constants');
|
||||
|
||||
if (this.get('document.protection') === constants.ProtectionType.Review) {
|
||||
if (this.get('page.status') === constants.ChangeState.Published) {
|
||||
page.set('relativeId', this.get('page.id'));
|
||||
}
|
||||
if (this.get('page.status') === constants.ChangeState.PendingNew) {
|
||||
page.set('relativeId', '');
|
||||
}
|
||||
}
|
||||
|
||||
this.set('editMode', false);
|
||||
this.get('onSavePage')(page, meta);
|
||||
let cb = this.get('onSavePage');
|
||||
cb(page, meta);
|
||||
},
|
||||
|
||||
onSavePageAsBlock(block) {
|
||||
this.attrs.onSavePageAsBlock(block);
|
||||
let cb = this.get('onSavePageAsBlock');
|
||||
cb(block);
|
||||
},
|
||||
|
||||
onCopyPage(documentId) {
|
||||
this.attrs.onCopyPage(this.get('page.id'), documentId);
|
||||
let cb = this.get('onCopyPage');
|
||||
cb(this.get('page.id'), documentId);
|
||||
},
|
||||
|
||||
onMovePage(documentId) {
|
||||
this.attrs.onMovePage(this.get('page.id'), documentId);
|
||||
let cb = this.get('onMovePage');
|
||||
cb(this.get('page.id'), documentId);
|
||||
},
|
||||
|
||||
onDeletePage(deleteChildren) {
|
||||
|
@ -74,16 +82,14 @@ export default Component.extend(NotifierMixin, TooltipMixin, {
|
|||
children: deleteChildren
|
||||
};
|
||||
|
||||
this.attrs.onDeletePage(params);
|
||||
let cb = this.get('onDeletePage');
|
||||
cb(params);
|
||||
},
|
||||
|
||||
// Calculate if user is editing page or a pending change as per approval process
|
||||
onEdit() {
|
||||
if (this.get('editMode')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.get('editMode')) return;
|
||||
this.get('toEdit', '');
|
||||
// this.set('pageId', this.get('page.id'));
|
||||
this.set('editMode', true);
|
||||
},
|
||||
|
||||
|
|
|
@ -9,101 +9,118 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { computed } from '@ember/object';
|
||||
import { schedule } from '@ember/runloop'
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
import tocUtil from '../../utils/toc';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
|
||||
export default Component.extend({
|
||||
export default Component.extend(TooltipMixin, {
|
||||
documentService: service('document'),
|
||||
document: {},
|
||||
folder: {},
|
||||
pages: [],
|
||||
currentPageId: '',
|
||||
state: {
|
||||
actionablePage: false,
|
||||
upDisabled: true,
|
||||
downDisabled: true,
|
||||
indentDisabled: true,
|
||||
outdentDisabled: true
|
||||
},
|
||||
isDesktop: false,
|
||||
emptyState: computed('pages', function () {
|
||||
return this.get('pages.length') === 0;
|
||||
}),
|
||||
isDesktop: false,
|
||||
canEdit: computed('permssions', 'document', function() {
|
||||
let constants = this.get('constants');
|
||||
let permissions = this.get('permissions');
|
||||
let authenticated = this.get('session.authenticated');
|
||||
let notEmpty = this.get('pages.length') > 0;
|
||||
|
||||
if (notEmpty && authenticated && permissions.get('documentEdit') && this.get('document.protection') === constants.ProtectionType.None) return true;
|
||||
if (notEmpty && authenticated && permissions.get('documentApprove') && this.get('document.protection') === constants.ProtectionType.Review) return true;
|
||||
|
||||
return false;
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.state = {
|
||||
actionablePage: false,
|
||||
upDisabled: true,
|
||||
downDisabled: true,
|
||||
indentDisabled: true,
|
||||
outdentDisabled: true,
|
||||
pageId: ''
|
||||
};
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.setState(this.get('currentPageId'));
|
||||
let cp = this.get('currentPageId');
|
||||
this.setState(is.empty(cp) ? '' : cp);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.setSize();
|
||||
this.eventBus.subscribe('documentPageAdded', this, 'onDocumentPageAdded');
|
||||
this.eventBus.subscribe('resized', this, 'onResize');
|
||||
this.attachResizer();
|
||||
this.eventBus.subscribe('resized', this, 'setSize');
|
||||
|
||||
this.setSize();
|
||||
this.renderTooltips();
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.eventBus.unsubscribe('documentPageAdded');
|
||||
this.eventBus.unsubscribe('resized');
|
||||
|
||||
let t = '#doc-toc';
|
||||
if (interact.isSet(t)) {
|
||||
interact(t).unset();
|
||||
}
|
||||
if (interact.isSet(t)) interact(t).unset();
|
||||
this.removeTooltips();
|
||||
},
|
||||
|
||||
onDocumentPageAdded(pageId) {
|
||||
this.send('onEntryClick', pageId);
|
||||
this.setSize();
|
||||
},
|
||||
|
||||
onResize() {
|
||||
onDocumentPageAdded(pageId) { // eslint-disable-line no-unused-vars
|
||||
this.setSize();
|
||||
},
|
||||
|
||||
setSize() {
|
||||
let isDesktop = $(window).width() >= 1800;
|
||||
this.set('isDesktop', isDesktop);
|
||||
schedule('afterRender', () => {
|
||||
let isDesktop = $(window).width() >= 1800;
|
||||
this.set('isDesktop', isDesktop);
|
||||
|
||||
if (isDesktop) {
|
||||
let h = $(window).height() - $("#nav-bar").height() - 140;
|
||||
$("#doc-toc").css('max-height', h);
|
||||
|
||||
let i = $("#doc-view").offset();
|
||||
|
||||
if (is.not.undefined(i)) {
|
||||
let l = i.left - 100;
|
||||
if (l > 350) l = 350;
|
||||
$("#doc-toc").width(l);
|
||||
if (isDesktop) {
|
||||
let h = $(window).height() - $("#nav-bar").height() - 140;
|
||||
$("#doc-toc").css('max-height', h);
|
||||
|
||||
let i = $("#doc-view").offset();
|
||||
|
||||
if (is.not.undefined(i)) {
|
||||
let l = i.left - 100;
|
||||
if (l > 350) l = 350;
|
||||
$("#doc-toc").width(l);
|
||||
|
||||
$("#doc-toc").css({
|
||||
'display': 'inline-block',
|
||||
'position': 'fixed',
|
||||
'width': l+'px',
|
||||
'height': 'auto',
|
||||
'transform': '',
|
||||
});
|
||||
|
||||
this.attachResizer();
|
||||
}
|
||||
} else {
|
||||
$("#doc-toc").css({
|
||||
'display': 'inline-block',
|
||||
'position': 'fixed',
|
||||
'width': l+'px',
|
||||
'height': 'auto',
|
||||
'transform': '',
|
||||
'display': 'block',
|
||||
'position': 'relative',
|
||||
'width': '100%',
|
||||
'height': '500px',
|
||||
'transform': 'none',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$("#doc-toc").css({
|
||||
'display': 'block',
|
||||
'position': 'relative',
|
||||
'width': '100%',
|
||||
'height': '500px',
|
||||
'transform': 'none',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
attachResizer() {
|
||||
schedule('afterRender', () => {
|
||||
let t = '#doc-toc';
|
||||
if (interact.isSet(t)) {
|
||||
interact(t).unset();
|
||||
}
|
||||
|
||||
interact('#doc-toc')
|
||||
.draggable({
|
||||
autoScroll: true,
|
||||
|
@ -166,15 +183,12 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
// Controls what user can do with the toc (left sidebar)
|
||||
// Identifies the target pages
|
||||
setState(pageId) {
|
||||
this.set('currentPageId', pageId);
|
||||
|
||||
let toc = this.get('pages');
|
||||
let page = _.findWhere(toc, { id: pageId });
|
||||
let state = tocUtil.getState(toc, page);
|
||||
let page = _.find(toc, function(i) { return i.get('page.id') === pageId; });
|
||||
let state = tocUtil.getState(toc, is.not.undefined(page) ? page.get('page') : page);
|
||||
|
||||
if (!this.get('permissions.documentEdit') || is.empty(pageId)) {
|
||||
if (!this.get('canEdit')) {
|
||||
state.actionablePage = false;
|
||||
state.upDisabled = state.downDisabled = state.indentDisabled = state.outdentDisabled = true;
|
||||
}
|
||||
|
@ -185,77 +199,77 @@ export default Component.extend({
|
|||
actions: {
|
||||
// Page up -- above pages shunt down
|
||||
pageUp() {
|
||||
if (this.get('state.upDisabled')) {
|
||||
let state = this.get('state');
|
||||
|
||||
if (state.upDisabled || this.get('document.protection') !== this.get('constants').ProtectionType.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = this.get('state');
|
||||
let pages = this.get('pages');
|
||||
let page = _.findWhere(pages, { id: this.get('currentPageId') });
|
||||
let pendingChanges = tocUtil.moveUp(state, pages, page);
|
||||
let page = _.find(pages, function(i) { return i.get('page.id') === state.pageId; });
|
||||
if (is.not.undefined(page)) page = page.get('page');
|
||||
|
||||
let pendingChanges = tocUtil.moveUp(state, pages, page);
|
||||
if (pendingChanges.length > 0) {
|
||||
this.attrs.onPageSequenceChange(pendingChanges);
|
||||
let cb = this.get('onPageSequenceChange');
|
||||
cb(state.pageId, pendingChanges);
|
||||
}
|
||||
},
|
||||
|
||||
// Move down -- pages below shift up
|
||||
pageDown() {
|
||||
if (this.get('state.downDisabled')) {
|
||||
return;
|
||||
}
|
||||
if (!this.get('canEdit')) return;
|
||||
|
||||
let state = this.get('state');
|
||||
var pages = this.get('pages');
|
||||
var page = _.findWhere(pages, { id: this.get('currentPageId') });
|
||||
let pendingChanges = tocUtil.moveDown(state, pages, page);
|
||||
let pages = this.get('pages');
|
||||
let page = _.find(pages, function(i) { return i.get('page.id') === state.pageId; });
|
||||
if (is.not.undefined(page)) page = page.get('page');
|
||||
|
||||
let pendingChanges = tocUtil.moveDown(state, pages, page);
|
||||
if (pendingChanges.length > 0) {
|
||||
this.attrs.onPageSequenceChange(pendingChanges);
|
||||
let cb = this.get('onPageSequenceChange');
|
||||
cb(state.pageId, pendingChanges);
|
||||
}
|
||||
},
|
||||
|
||||
// Indent -- changes a page from H2 to H3, etc.
|
||||
pageIndent() {
|
||||
if (this.get('state.indentDisabled')) {
|
||||
return;
|
||||
}
|
||||
if (!this.get('canEdit')) return;
|
||||
|
||||
let state = this.get('state');
|
||||
var pages = this.get('pages');
|
||||
var page = _.findWhere(pages, { id: this.get('currentPageId') });
|
||||
let pendingChanges = tocUtil.indent(state, pages, page);
|
||||
let pages = this.get('pages');
|
||||
let page = _.find(pages, function(i) { return i.get('page.id') === state.pageId; });
|
||||
if (is.not.undefined(page)) page = page.get('page');
|
||||
|
||||
let pendingChanges = tocUtil.indent(state, pages, page);
|
||||
if (pendingChanges.length > 0) {
|
||||
this.attrs.onPageLevelChange(pendingChanges);
|
||||
let cb = this.get('onPageLevelChange');
|
||||
cb(state.pageId, pendingChanges);
|
||||
}
|
||||
},
|
||||
|
||||
// Outdent -- changes a page from H3 to H2, etc.
|
||||
pageOutdent() {
|
||||
if (this.get('state.outdentDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.get('canEdit')) return;
|
||||
|
||||
let state = this.get('state');
|
||||
var pages = this.get('pages');
|
||||
var page = _.findWhere(pages, { id: this.get('currentPageId') });
|
||||
let pendingChanges = tocUtil.outdent(state, pages, page);
|
||||
let pages = this.get('pages');
|
||||
let page = _.find(pages, function(i) { return i.get('page.id') === state.pageId; });
|
||||
if (is.not.undefined(page)) page = page.get('page');
|
||||
|
||||
let pendingChanges = tocUtil.outdent(state, pages, page);
|
||||
if (pendingChanges.length > 0) {
|
||||
this.attrs.onPageLevelChange(pendingChanges);
|
||||
let cb = this.get('onPageLevelChange');
|
||||
cb(state.pageId, pendingChanges);
|
||||
}
|
||||
},
|
||||
|
||||
onEntryClick(id) {
|
||||
if (id !== '') {
|
||||
let jumpTo = "#page-" + id;
|
||||
this.set('tab', 'content');
|
||||
if (!$(jumpTo).inView()) {
|
||||
$(jumpTo).velocity("scroll", { duration: 250, offset: -100 });
|
||||
}
|
||||
this.setState(id);
|
||||
}
|
||||
onGotoPage(id) {
|
||||
if (id === '') return;
|
||||
this.setState(id);
|
||||
|
||||
let cb = this.get('onShowPage');
|
||||
cb(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,66 +9,76 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import Component from '@ember/component';
|
||||
import $ from 'jquery';
|
||||
import { computed } from '@ember/object';
|
||||
import { debounce } from '@ember/runloop';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { A } from "@ember/array"
|
||||
import ModalMixin from '../../mixins/modal';
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend(ModalMixin, {
|
||||
documentService: service('document'),
|
||||
searchService: service('search'),
|
||||
router: service(),
|
||||
deleteChildren: false,
|
||||
blockTitle: "",
|
||||
blockExcerpt: "",
|
||||
documentList: A([]), //includes the current document
|
||||
documentListOthers: A([]), //excludes the current document
|
||||
hasMenuPermissions: computed('permissions', function() {
|
||||
let permissions = this.get('permissions');
|
||||
return permissions.get('documentDelete') || permissions.get('documentCopy') ||
|
||||
permissions.get('documentMove') || permissions.get('documentTemplate');
|
||||
canEdit: false,
|
||||
canDelete: false,
|
||||
canMove: false,
|
||||
docSearchFilter: '',
|
||||
onKeywordChange: function () {
|
||||
debounce(this, this.searchDocs, 750);
|
||||
}.observes('docSearchFilter'),
|
||||
emptySearch: computed('docSearchResults', function() {
|
||||
return this.get('docSearchResults.length') === 0;
|
||||
}),
|
||||
hasMenuPermissions: computed('permissions', 'userPendingItem', 'canEdit', 'canMove', 'canDelete', function() {
|
||||
let permissions = this.get('permissions');
|
||||
|
||||
return permissions.get('documentCopy') || permissions.get('documentTemplate') ||
|
||||
this.get('canEdit') || this.get('canMove') || this.get('canDelete');
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.docSearchResults = [];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
// Fetch document targets once
|
||||
if (this.get('documentList').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalInputFocus('#publish-page-modal-' + this.get('page.id'), '#block-title-' + this.get('page.id'));
|
||||
this.load();
|
||||
},
|
||||
|
||||
load() {
|
||||
let permissions = this.get('permissions');
|
||||
if (permissions.get('documentMove') || permissions.get('documentCopy')) {
|
||||
this.get('documentService').getPageMoveCopyTargets().then((d) => {
|
||||
let me = this.get('document');
|
||||
this.set('canEdit', permissions.get('documentEdit'));
|
||||
this.set('canDelete', permissions.get('documentDelete'));
|
||||
this.set('canMove', permissions.get('documentMove'));
|
||||
},
|
||||
|
||||
d.forEach((i) => {
|
||||
i.set('selected', false);
|
||||
});
|
||||
|
||||
if (this.get('isDestroyed') || this.get('isDestroying')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('documentList', A(d));
|
||||
this.set('documentListOthers', A(d.filter((item) => item.get('id') !== me.get('id'))));
|
||||
});
|
||||
}
|
||||
searchDocs() {
|
||||
let payload = { keywords: this.get('docSearchFilter').trim(), doc: true };
|
||||
if (payload.keywords.length == 0) return;
|
||||
|
||||
this.get('searchService').find(payload).then((response)=> {
|
||||
this.set('docSearchResults', response);
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
onEdit() {
|
||||
this.attrs.onEdit();
|
||||
let page = this.get('page');
|
||||
|
||||
if (page.get('pageType') == this.get('constants').PageType.Tab) {
|
||||
this.get('router').transitionTo('document.section', page.get('id'));
|
||||
} else {
|
||||
let cb = this.get('onEdit');
|
||||
cb();
|
||||
}
|
||||
},
|
||||
|
||||
onDeletePage() {
|
||||
this.attrs.onDeletePage(this.get('deleteChildren'));
|
||||
|
||||
this.load();
|
||||
let cb = this.get('onDeletePage');
|
||||
cb(this.get('deleteChildren'));
|
||||
|
||||
this.modalClose('#delete-page-modal-' + this.get('page.id'));
|
||||
},
|
||||
|
@ -103,57 +113,60 @@ export default Component.extend(ModalMixin, {
|
|||
externalSource: pm.get('externalSource')
|
||||
};
|
||||
|
||||
this.attrs.onSavePageAsBlock(block);
|
||||
let cb = this.get('onSavePageAsBlock');
|
||||
cb(block);
|
||||
|
||||
this.set('blockTitle', '');
|
||||
this.set('blockExcerpt', '');
|
||||
$(titleElem).removeClass('is-invalid');
|
||||
$(excerptElem).removeClass('is-invalid');
|
||||
|
||||
this.load();
|
||||
|
||||
this.modalClose('#publish-page-modal-' + this.get('page.id'));
|
||||
|
||||
let refresh = this.get('refresh');
|
||||
refresh();
|
||||
});
|
||||
},
|
||||
|
||||
onSelectSearchResult(documentId) {
|
||||
let results = this.get('docSearchResults');
|
||||
results.forEach((d) => {
|
||||
d.set('selected', d.get('documentId') === documentId);
|
||||
});
|
||||
this.set('docSearchResults', results);
|
||||
},
|
||||
|
||||
onCopyPage() {
|
||||
// can't proceed if no data
|
||||
if (this.get('documentList.length') === 0) {
|
||||
return;
|
||||
}
|
||||
let item = this.get('docSearchResults').findBy('selected', true);
|
||||
let documentId = is.not.undefined(item) ? item.get('documentId') : '';
|
||||
|
||||
let targetDocumentId = this.get('documentList').findBy('selected', true).get('id');
|
||||
|
||||
// fall back to self
|
||||
if (is.null(targetDocumentId)) {
|
||||
targetDocumentId = this.get('document.id');
|
||||
}
|
||||
|
||||
this.attrs.onCopyPage(targetDocumentId);
|
||||
|
||||
this.load();
|
||||
if (is.empty(documentId)) return;
|
||||
|
||||
this.modalClose('#copy-page-modal-' + this.get('page.id'));
|
||||
|
||||
let cb = this.get('onCopyPage');
|
||||
cb(documentId);
|
||||
|
||||
let refresh = this.get('refresh');
|
||||
refresh();
|
||||
},
|
||||
|
||||
onMovePage() {
|
||||
// can't proceed if no data
|
||||
if (this.get('documentListOthers.length') === 0) {
|
||||
return;
|
||||
}
|
||||
let item = this.get('docSearchResults').findBy('selected', true);
|
||||
let documentId = is.not.undefined(item) ? item.get('documentId') : '';
|
||||
|
||||
let targetDocumentId = this.get('documentListOthers').findBy('selected', true).get('id');
|
||||
if (is.empty(documentId)) return;
|
||||
|
||||
// fall back to first document
|
||||
if (is.null(targetDocumentId)) {
|
||||
targetDocumentId = this.get('documentListOthers')[0].get('id');
|
||||
}
|
||||
|
||||
this.attrs.onMovePage(targetDocumentId);
|
||||
|
||||
this.load();
|
||||
// can't move into self
|
||||
if (documentId === this.get('document.id')) return;
|
||||
|
||||
this.modalClose('#move-page-modal-' + this.get('page.id'));
|
||||
}
|
||||
|
||||
let cb = this.get('onMovePage');
|
||||
cb(documentId);
|
||||
|
||||
let refresh = this.get('refresh');
|
||||
refresh();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,24 +18,25 @@ export default Component.extend({
|
|||
documentService: service('document'),
|
||||
appMeta: service(),
|
||||
hasAttachments: notEmpty('files'),
|
||||
deleteAttachment: {
|
||||
id: "",
|
||||
name: "",
|
||||
},
|
||||
canShow: computed('permissions', 'files', function() {
|
||||
return this.get('files.length') > 0 || this.get('permissions.documentEdit');
|
||||
canEdit: computed('permissions', 'document.protection', function() {
|
||||
return this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit');
|
||||
}),
|
||||
showDialog: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.getAttachments();
|
||||
|
||||
this.deleteAttachment = {
|
||||
id: "",
|
||||
name: "",
|
||||
};
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (!this.get('permissions.documentEdit')) {
|
||||
if (!this.get('permissions.documentEdit') || this.get('document.protection') === this.get('constants').ProtectionType.Lock) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -78,10 +79,6 @@ export default Component.extend({
|
|||
this.set('drop', dzone);
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
getAttachments() {
|
||||
this.get('documentService').getAttachments(this.get('document.id')).then((files) => {
|
||||
this.set('files', files);
|
||||
|
|
|
@ -9,11 +9,14 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { notEmpty, empty } from '@ember/object/computed';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed } from '@ember/object';
|
||||
import Component from '@ember/component';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
import models from '../../utils/model';
|
||||
|
||||
export default Component.extend(TooltipMixin, {
|
||||
documentService: service('document'),
|
||||
|
@ -28,11 +31,15 @@ export default Component.extend(TooltipMixin, {
|
|||
toEdit: '',
|
||||
showDeleteBlockDialog: false,
|
||||
deleteBlockId: '',
|
||||
canEdit: computed('permissions', 'document.protection', function() {
|
||||
let canEdit = this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit');
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.loadBlocks();
|
||||
},
|
||||
if (canEdit) this.setupAddWizard();
|
||||
return canEdit;
|
||||
}),
|
||||
hasBlocks: computed('blocks', function() {
|
||||
return this.get('blocks.length') > 0;
|
||||
}),
|
||||
|
||||
didRender() {
|
||||
this._super(...arguments);
|
||||
|
@ -41,20 +48,22 @@ export default Component.extend(TooltipMixin, {
|
|||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
this.setupAddWizard();
|
||||
|
||||
if (this.attrs.onGotoPage !== null) {
|
||||
this.attrs.onGotoPage(this.get('pageId'));
|
||||
if (this.get('session.authenticated')) {
|
||||
this.setupAddWizard();
|
||||
this.renderTooltips();
|
||||
}
|
||||
|
||||
this.renderTooltips();
|
||||
this.jumpToSection();
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
$('.start-section:not(.start-section-empty-state)').off('.hoverIntent');
|
||||
|
||||
this.removeTooltips();
|
||||
if (this.get('session.authenticated')) {
|
||||
$('.start-section:not(.start-section-empty-state)').off('.hoverIntent');
|
||||
this.removeTooltips();
|
||||
}
|
||||
},
|
||||
|
||||
contentLinkHandler() {
|
||||
|
@ -73,7 +82,9 @@ export default Component.extend(TooltipMixin, {
|
|||
link.orphan = true;
|
||||
} else {
|
||||
if (link.linkType === "section") {
|
||||
self.attrs.onGotoPage(link.targetId);
|
||||
self.get('currentPageId', link.targetId)
|
||||
self.jumpToSection();
|
||||
// self.get('browser').scrollTo(`#page-${link.targetId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,80 +116,112 @@ export default Component.extend(TooltipMixin, {
|
|||
},
|
||||
|
||||
addSection(model) {
|
||||
// calculate sequence of page (position in document)
|
||||
let sequence = 0;
|
||||
let level = 1;
|
||||
let beforePage = this.get('beforePage');
|
||||
let constants = this.get('constants');
|
||||
|
||||
// calculate sequence of page (position in document)
|
||||
if (is.not.null(beforePage)) {
|
||||
level = beforePage.get('level');
|
||||
|
||||
// get any page before the beforePage so we can insert this new section between them
|
||||
let index = _.findIndex(this.get('pages'), function(p) { return p.get('id') === beforePage.get('id'); });
|
||||
let index = _.findIndex(this.get('pages'), function(item) { return item.get('page.id') === beforePage.get('id'); });
|
||||
|
||||
if (index !== -1) {
|
||||
let beforeBeforePage = this.get('pages')[index-1];
|
||||
|
||||
if (is.not.undefined(beforeBeforePage)) {
|
||||
sequence = (beforePage.get('sequence') + beforeBeforePage.get('sequence')) / 2;
|
||||
sequence = (beforePage.get('sequence') + beforeBeforePage.get('page.sequence')) / 2;
|
||||
} else {
|
||||
sequence = beforePage.get('sequence') / 2;
|
||||
}
|
||||
|
||||
model.page.set('sequence', sequence);
|
||||
model.page.set('level', level);
|
||||
}
|
||||
}
|
||||
|
||||
model.page.sequence = sequence;
|
||||
model.page.level = level;
|
||||
if (this.get('document.protection') === constants.ProtectionType.Review) {
|
||||
model.page.set('status', model.page.get('relativeId') === '' ? constants.ChangeState.PendingNew : constants.ChangeState.Pending);
|
||||
}
|
||||
|
||||
this.send('onHideSectionWizard');
|
||||
|
||||
return this.get('onInsertSection')(model);
|
||||
},
|
||||
|
||||
loadBlocks() {
|
||||
if (is.not.undefined(this.get('folder'))) {
|
||||
this.get('sectionService').getSpaceBlocks(this.get('folder.id')).then((blocks) => {
|
||||
if (this.get('isDestroyed') || this.get('isDestroying')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('blocks', blocks);
|
||||
this.set('hasBlocks', blocks.get('length') > 0);
|
||||
});
|
||||
jumpToSection() {
|
||||
let cp = this.get('currentPageId');
|
||||
if (is.not.empty(cp) && is.not.undefined(cp) && is.not.null(cp)) {
|
||||
this.get('browser').scrollTo(`#page-${cp}`)
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
onSavePageAsBlock(block) {
|
||||
const promise = this.attrs.onSavePageAsBlock(block);
|
||||
let cb = this.get('onSavePageAsBlock');
|
||||
const promise = cb(block);
|
||||
|
||||
promise.then(() => {
|
||||
this.loadBlocks();
|
||||
let refresh = this.get('refresh');
|
||||
refresh();
|
||||
});
|
||||
},
|
||||
|
||||
onCopyPage(pageId, documentId) {
|
||||
this.attrs.onCopyPage(pageId, documentId);
|
||||
let cb = this.get('onCopyPage');
|
||||
cb(pageId, documentId);
|
||||
},
|
||||
|
||||
onMovePage(pageId, documentId) {
|
||||
this.attrs.onMovePage(pageId, documentId);
|
||||
let cb = this.get('onMovePage');
|
||||
cb(pageId, documentId);
|
||||
},
|
||||
|
||||
onDeletePage(params) {
|
||||
this.attrs.onDeletePage(params);
|
||||
let cb = this.get('onDeletePage');
|
||||
cb(params);
|
||||
},
|
||||
|
||||
onSavePage(page, meta) {
|
||||
this.set('toEdit', '');
|
||||
this.attrs.onSavePage(page, meta);
|
||||
let document = this.get('document');
|
||||
let constants = this.get('constants');
|
||||
|
||||
switch (document.get('protection')) {
|
||||
case constants.ProtectionType.Lock:
|
||||
break;
|
||||
case constants.ProtectionType.Review:
|
||||
// detect edits to newly created pending page
|
||||
if (page.get('relativeId') === '' && page.get('status') === constants.ChangeState.PendingNew) {
|
||||
// new page, edits
|
||||
this.set('toEdit', '');
|
||||
let cb = this.get('onSavePage');
|
||||
cb(page, meta);
|
||||
} else if (page.get('relativeId') !== '' && page.get('status') === constants.ChangeState.Published) {
|
||||
// existing page, first edit
|
||||
const promise = this.addSection({ page: page, meta: meta });
|
||||
promise.then((/*id*/) => { this.set('toEdit', ''); });
|
||||
} else if (page.get('relativeId') !== '' && page.get('status') === constants.ChangeState.Pending) {
|
||||
// existing page, subsequent edits
|
||||
this.set('toEdit', '');
|
||||
let cb = this.get('onSavePage');
|
||||
cb(page, meta);
|
||||
}
|
||||
break;
|
||||
case constants.ProtectionType.None:
|
||||
// for un-protected documents, edits welcome!
|
||||
this.set('toEdit', '');
|
||||
// let cb2 = this.get('onSavePage');
|
||||
// cb2(page, meta);
|
||||
this.attrs.onSavePage(page, meta); // eslint-disable-line ember/no-attrs-in-components
|
||||
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onShowSectionWizard(page) {
|
||||
if (is.undefined(page)) {
|
||||
page = { id: '0' };
|
||||
}
|
||||
|
||||
this.set('pageId', '');
|
||||
if (is.undefined(page)) page = { id: '0' };
|
||||
|
||||
let beforePage = this.get('beforePage');
|
||||
if (is.not.null(beforePage) && $("#new-section-wizard").is(':visible') && beforePage.get('id') === page.id) {
|
||||
|
@ -217,15 +260,11 @@ export default Component.extend(TooltipMixin, {
|
|||
return;
|
||||
}
|
||||
|
||||
let page = {
|
||||
documentId: this.get('document.id'),
|
||||
title: sectionName,
|
||||
level: 1,
|
||||
sequence: 0, // calculated elsewhere
|
||||
body: "",
|
||||
contentType: section.get('contentType'),
|
||||
pageType: section.get('pageType')
|
||||
};
|
||||
let page = models.PageModel.create();
|
||||
page.set('documentId', this.get('document.id'));
|
||||
page.set('title', sectionName);
|
||||
page.set('contentType', section.get('contentType'));
|
||||
page.set('pageType', section.get('pageType'));
|
||||
|
||||
let meta = {
|
||||
documentId: this.get('document.id'),
|
||||
|
@ -240,14 +279,7 @@ export default Component.extend(TooltipMixin, {
|
|||
|
||||
const promise = this.addSection(model);
|
||||
promise.then((id) => {
|
||||
this.set('pageId', id);
|
||||
|
||||
if (model.page.pageType === 'section') {
|
||||
this.set('toEdit', id);
|
||||
} else {
|
||||
this.set('toEdit', '');
|
||||
}
|
||||
|
||||
this.set('toEdit', model.page.pageType === 'section' ? id: '');
|
||||
this.setupAddWizard();
|
||||
});
|
||||
},
|
||||
|
@ -259,16 +291,13 @@ export default Component.extend(TooltipMixin, {
|
|||
return;
|
||||
}
|
||||
|
||||
let page = {
|
||||
documentId: this.get('document.id'),
|
||||
title: `${block.get('title')}`,
|
||||
level: 1,
|
||||
sequence: 0, // calculated elsewhere
|
||||
body: block.get('body'),
|
||||
contentType: block.get('contentType'),
|
||||
pageType: block.get('pageType'),
|
||||
blockId: block.get('id')
|
||||
};
|
||||
let page = models.PageModel.create();
|
||||
page.set('documentId', this.get('document.id'));
|
||||
page.set('title', `${block.get('title')}`);
|
||||
page.set('body', block.get('body'));
|
||||
page.set('contentType', block.get('contentType'));
|
||||
page.set('pageType', block.get('pageType'));
|
||||
page.set('blockId', block.get('id'));
|
||||
|
||||
let meta = {
|
||||
documentId: this.get('document.id'),
|
||||
|
@ -283,9 +312,7 @@ export default Component.extend(TooltipMixin, {
|
|||
};
|
||||
|
||||
const promise = this.addSection(model);
|
||||
promise.then((id) => {
|
||||
this.set('pageId', id);
|
||||
|
||||
promise.then((id) => { // eslint-disable-line no-unused-vars
|
||||
this.setupAddWizard();
|
||||
});
|
||||
},
|
||||
|
@ -299,11 +326,14 @@ export default Component.extend(TooltipMixin, {
|
|||
this.set('showDeleteBlockDialog', false);
|
||||
|
||||
let id = this.get('deleteBlockId');
|
||||
const promise = this.attrs.onDeleteBlock(id);
|
||||
|
||||
let cb = this.get('onDeleteBlock');
|
||||
let promise = cb(id);
|
||||
|
||||
promise.then(() => {
|
||||
this.set('deleteBlockId', '');
|
||||
this.loadBlocks();
|
||||
let refresh = this.get('refresh');
|
||||
refresh();
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
|
@ -16,7 +16,6 @@ import ModalMixin from '../../mixins/modal';
|
|||
|
||||
export default Component.extend(ModalMixin, {
|
||||
documentService: service('document'),
|
||||
revisions: [],
|
||||
revision: null,
|
||||
diff: '',
|
||||
hasRevisions: computed('revisions', function() {
|
||||
|
@ -26,6 +25,11 @@ export default Component.extend(ModalMixin, {
|
|||
return this.get('diff').length > 0;
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.revisions = [];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.fetchRevisions();
|
||||
|
@ -65,7 +69,8 @@ export default Component.extend(ModalMixin, {
|
|||
|
||||
onRollback() {
|
||||
let revision = this.get('revision');
|
||||
this.attrs.onRollback(revision.pageId, revision.id);
|
||||
let cb = this.get('onRollback');
|
||||
cb(revision.pageId, revision.id);
|
||||
|
||||
this.modalClose('#document-rollback-modal');
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
import TextField from '@ember/component/text-field';
|
||||
|
||||
export default TextField.extend({
|
||||
becomeFocused: function() {
|
||||
this.$().focus();
|
||||
}.on('didInsertElement')
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
this.$().focus();
|
||||
}
|
||||
});
|
|
@ -12,7 +12,8 @@
|
|||
import TextArea from '@ember/component/text-area';
|
||||
|
||||
export default TextArea.extend({
|
||||
becomeFocused: function() {
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
this.$().focus();
|
||||
}.on('didInsertElement')
|
||||
}
|
||||
});
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
|
@ -22,7 +23,11 @@ export default Component.extend(ModalMixin, TooltipMixin, {
|
|||
newCategory: '',
|
||||
deleteId: '',
|
||||
dropdown: null,
|
||||
users: [],
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.users = [];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
|
|
@ -12,9 +12,6 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
documentTags: [],
|
||||
tagz: [],
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
let tagz = [];
|
||||
|
|
|
@ -19,13 +19,13 @@ export default Component.extend({
|
|||
selectedDocuments: A([]),
|
||||
selectedCaption: 'document',
|
||||
|
||||
showAdd: computed('permissions', 'documents', function() {
|
||||
showAdd: computed('permissions.documentAdd', 'documents', function() {
|
||||
return this.get('documents.length') === 0 && this.get('permissions.documentAdd');
|
||||
}),
|
||||
showLockout: computed('permissions', 'documents', function() {
|
||||
showLockout: computed('permissions.documentAdd', 'documents', function() {
|
||||
return this.get('documents.length') === 0 && !this.get('permissions.documentAdd');
|
||||
}),
|
||||
hasDocumentActions: computed('permissions', function() {
|
||||
hasDocumentActions: computed('permissions.{documentDelete,documentMove}', function() {
|
||||
return this.get('permissions.documentDelete') || this.get('permissions.documentMove');
|
||||
}),
|
||||
|
||||
|
@ -48,7 +48,8 @@ export default Component.extend({
|
|||
this.set('selectedDocuments', A([]));
|
||||
this.set('showDeleteDialog', false);
|
||||
|
||||
this.attrs.onDeleteDocument(list);
|
||||
let cb = this.get('onDeleteDocument');
|
||||
cb(list);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
@ -72,7 +73,9 @@ export default Component.extend({
|
|||
|
||||
this.set('showMoveDialog', false);
|
||||
this.set('selectedDocuments', A([]));
|
||||
this.attrs.onMoveDocument(list, moveSpaceId);
|
||||
|
||||
let cb = this.get('onMoveDocument');
|
||||
cb(list, moveSpaceId);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
|
|
@ -10,12 +10,11 @@
|
|||
// https://documize.com
|
||||
|
||||
import { setProperties } from '@ember/object';
|
||||
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import NotifierMixin from '../../mixins/notifier';
|
||||
import ModalMixin from '../../mixins/modal';
|
||||
|
||||
export default Component.extend(NotifierMixin, {
|
||||
export default Component.extend(ModalMixin, {
|
||||
folderService: service('folder'),
|
||||
userService: service('user'),
|
||||
appMeta: service(),
|
||||
|
@ -42,7 +41,8 @@ export default Component.extend(NotifierMixin, {
|
|||
documentDelete: false,
|
||||
documentMove: false,
|
||||
documentCopy: false,
|
||||
documentTemplate: false
|
||||
documentTemplate: false,
|
||||
documentApprove: false,
|
||||
};
|
||||
|
||||
let data = this.get('store').normalize('space-permission', u)
|
||||
|
@ -63,8 +63,9 @@ export default Component.extend(NotifierMixin, {
|
|||
documentDelete: false,
|
||||
documentMove: false,
|
||||
documentCopy: false,
|
||||
documentTemplate: false
|
||||
};
|
||||
documentTemplate: false,
|
||||
documentApprove: false,
|
||||
};
|
||||
|
||||
let data = this.get('store').normalize('space-permission', u)
|
||||
folderPermissions.pushObject(this.get('store').push(data));
|
||||
|
@ -96,7 +97,7 @@ export default Component.extend(NotifierMixin, {
|
|||
let hasEveryone = _.find(permissions, function (permission) {
|
||||
return permission.get('userId') === "0" &&
|
||||
(permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') ||
|
||||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate'));
|
||||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove'));
|
||||
});
|
||||
|
||||
// see if more than oen user is granted access to space (excluding everyone)
|
||||
|
@ -104,28 +105,23 @@ export default Component.extend(NotifierMixin, {
|
|||
permissions.forEach((permission) => {
|
||||
if (permission.get('userId') !== "0" &&
|
||||
(permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') ||
|
||||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate'))) {
|
||||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove'))) {
|
||||
roleCount += 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (is.not.undefined(hasEveryone)) {
|
||||
folder.markAsPublic();
|
||||
this.showNotification('Marked space as public');
|
||||
} else {
|
||||
if (roleCount > 1) {
|
||||
folder.markAsRestricted();
|
||||
this.showNotification('Marked space as protected');
|
||||
} else {
|
||||
folder.markAsPrivate();
|
||||
this.showNotification('Marked space as private');
|
||||
}
|
||||
}
|
||||
|
||||
this.get('folderService').savePermissions(folder.get('id'), payload).then(() => {
|
||||
this.showNotification('Saved permissions');
|
||||
$('#space-permission-modal').modal('hide');
|
||||
$('#space-permission-modal').modal('dispose');
|
||||
this.modalClose('#space-permission-modal');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty } from '@ember/object/computed';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
|
|
@ -23,12 +23,16 @@ export default Component.extend(AuthMixin, {
|
|||
folderService: service('folder'),
|
||||
localStorage: service('localStorage'),
|
||||
hasCategories: gt('categories.length', 0),
|
||||
filteredDocs: [],
|
||||
categoryLinkName: 'Manage',
|
||||
spaceSettings: computed('permissions', function() {
|
||||
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.filteredDocs = [];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.setup();
|
||||
|
@ -108,7 +112,8 @@ export default Component.extend(AuthMixin, {
|
|||
});
|
||||
|
||||
this.set('documents', documents);
|
||||
this.attrs.onRefresh();
|
||||
let cb = this.get('onRefresh');
|
||||
cb();
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty, and } from '@ember/object/computed';
|
||||
|
||||
import { set } from '@ember/object';
|
||||
import Component from '@ember/component';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
|
@ -114,7 +114,7 @@ export default Component.extend({
|
|||
$(".stage-3").fadeIn();
|
||||
// $("#spinner-1").show();
|
||||
|
||||
var payload = '{ "Password": "' + $("#stage-2-password").val() + '", "Serial": "' + self.serial + '", "Firstname": "' + $("#stage-1-firstname").val() + '", "Lastname": "' + $("#stage-1-lastname").val() + '" }';
|
||||
var payload = '{ "password": "' + $("#stage-2-password").val() + '", "serial": "' + self.serial + '", "firstname": "' + $("#stage-1-firstname").val() + '", "lastname": "' + $("#stage-1-lastname").val() + '" }';
|
||||
var password = $("#stage-2-password").val();
|
||||
|
||||
self.get('folderService').onboard(self.folderId, payload).then(function(user) {
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty, and } from '@ember/object/computed';
|
||||
|
||||
import Component from '@ember/component';
|
||||
import { isEqual, isEmpty } from '@ember/utils';
|
||||
import { set } from '@ember/object';
|
||||
|
|
|
@ -12,9 +12,13 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
results: [],
|
||||
resultPhrase: "",
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.results = [];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
let docs = this.get('results');
|
||||
let duped = [];
|
||||
|
|
|
@ -24,7 +24,8 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
@ -33,7 +34,8 @@ export default Component.extend({
|
|||
page.set('title', title);
|
||||
meta.set('rawBody', this.get("data"));
|
||||
|
||||
this.attrs.onAction(page, meta);
|
||||
let cb = this.get('onAction');
|
||||
cb(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty } from '@ember/object/computed';
|
||||
import { computed } from '@ember/object';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
|
@ -75,21 +76,25 @@ export default Component.extend(TooltipMixin, ModalMixin, {
|
|||
return;
|
||||
}
|
||||
|
||||
this.attrs.onAction(this.get('page.title'));
|
||||
let cb = this.get('onAction');
|
||||
cb(this.get('page.title'));
|
||||
},
|
||||
|
||||
onCancel() {
|
||||
if (this.attrs.isDirty() !== null && this.attrs.isDirty()) {
|
||||
let isDirty = this.get('isDirty');
|
||||
if (isDirty() !== null && isDirty()) {
|
||||
this.modalOpen('#discard-modal-' + this.get('page.id'), {show: true});
|
||||
return;
|
||||
}
|
||||
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onDiscard() {
|
||||
this.modalClose('#discard-modal-' + this.get('page.id'));
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onPreview() {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty } from '@ember/object/computed';
|
||||
import Component from '@ember/component';
|
||||
import ModalMixin from '../../mixins/modal';
|
||||
|
@ -44,19 +45,22 @@ export default Component.extend(ModalMixin, {
|
|||
|
||||
actions: {
|
||||
onCancel() {
|
||||
if (this.attrs.isDirty() !== null && this.attrs.isDirty()) {
|
||||
let isDirty = this.get('isDirty');
|
||||
if (isDirty() !== null && isDirty()) {
|
||||
this.modalOpen('#discard-modal', {show: true});
|
||||
return;
|
||||
}
|
||||
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onDiscard() {
|
||||
this.modalClose('#discard-modal');
|
||||
this.attrs.onCancel();
|
||||
},
|
||||
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction() {
|
||||
if (this.get('busy')) {
|
||||
|
@ -73,7 +77,8 @@ export default Component.extend(ModalMixin, {
|
|||
return;
|
||||
}
|
||||
|
||||
this.attrs.onAction(this.get('page.title'));
|
||||
let cb = this.get('onAction');
|
||||
cb(this.get('page.title'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ import TooltipMixin from '../../../mixins/tooltip';
|
|||
export default Component.extend(TooltipMixin, {
|
||||
isDirty: false,
|
||||
pageBody: "",
|
||||
syntaxOptions: [],
|
||||
|
||||
codeSyntax: null,
|
||||
codeEditor: null,
|
||||
editorId: computed('page', function () {
|
||||
|
@ -29,7 +29,8 @@ export default Component.extend(TooltipMixin, {
|
|||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this._super(...arguments);
|
||||
this.syntaxOptions = [];
|
||||
|
||||
let self = this;
|
||||
let rawBody = this.get('meta.rawBody');
|
||||
|
@ -128,7 +129,8 @@ export default Component.extend(TooltipMixin, {
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
@ -138,7 +140,8 @@ export default Component.extend(TooltipMixin, {
|
|||
page.set('title', title);
|
||||
page.set('body', meta.get('rawBody'));
|
||||
|
||||
this.attrs.onAction(page, meta);
|
||||
let cb = this.get('onAction');
|
||||
cb(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { set } from '@ember/object';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
@ -21,9 +22,13 @@ export default Component.extend(SectionMixin, TooltipMixin, {
|
|||
isDirty: false,
|
||||
waiting: false,
|
||||
authenticated: false,
|
||||
user: {},
|
||||
workspaces: [],
|
||||
config: {},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.user = {};
|
||||
this.workspaces = [];
|
||||
this.config = {};
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
let config = {};
|
||||
|
@ -195,7 +200,8 @@ export default Component.extend(SectionMixin, TooltipMixin, {
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
@ -206,7 +212,8 @@ export default Component.extend(SectionMixin, TooltipMixin, {
|
|||
meta.set('config', JSON.stringify(this.get('config')));
|
||||
meta.set('externalSource', true);
|
||||
|
||||
this.attrs.onAction(page, meta);
|
||||
let cb = this.get('onAction');
|
||||
cb(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import EmberObject from '@ember/object';
|
||||
import { A } from '@ember/array';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
@ -21,9 +22,13 @@ export default Component.extend(SectionMixin, NotifierMixin, {
|
|||
isDirty: false,
|
||||
busy: false,
|
||||
authenticated: false,
|
||||
config: {},
|
||||
owners: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.config = {};
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
let self = this;
|
||||
let page = this.get('page');
|
||||
|
@ -227,7 +232,8 @@ export default Component.extend(SectionMixin, NotifierMixin, {
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
|
|
@ -111,7 +111,8 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
@ -120,7 +121,8 @@ export default Component.extend({
|
|||
page.set('title', title);
|
||||
meta.set('rawBody', this.getBody());
|
||||
|
||||
this.attrs.onAction(page, meta);
|
||||
let cb = this.get('onAction');
|
||||
cb(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -21,8 +21,12 @@ export default Component.extend(SectionMixin, NotifierMixin, {
|
|||
isDirty: false,
|
||||
waiting: false,
|
||||
authenticated: false,
|
||||
config: {},
|
||||
items: {},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.config = {};
|
||||
this.items = {};
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
let config = {};
|
||||
|
@ -48,9 +52,9 @@ export default Component.extend(SectionMixin, NotifierMixin, {
|
|||
|
||||
displayError(reason) {
|
||||
if (netUtil.isAjaxAccessError(reason)) {
|
||||
this.showNotification(`Unable to authenticate`);
|
||||
// this.showNotification(`Unable to authenticate`);
|
||||
} else {
|
||||
this.showNotification(`Something went wrong, try again!`);
|
||||
// this.showNotification(`Something went wrong, try again!`);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -140,7 +144,8 @@ export default Component.extend(SectionMixin, NotifierMixin, {
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
@ -176,7 +181,7 @@ export default Component.extend(SectionMixin, NotifierMixin, {
|
|||
}, function (reason) { // eslint-disable-line no-unused-vars
|
||||
self.set('authenticated', false);
|
||||
self.set('waiting', false);
|
||||
self.showNotification(`Something went wrong, try again!`);
|
||||
// self.showNotification(`Something went wrong, try again!`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
|
@ -38,8 +39,10 @@ export default Component.extend({
|
|||
tableResizerOffset: 10
|
||||
});
|
||||
|
||||
$(id).on('froalaEditor.contentChanged', () => {
|
||||
this.set('isDirty', true);
|
||||
schedule('afterRender', function() {
|
||||
$(id).on('froalaEditor.contentChanged', () => {
|
||||
this.set('isDirty', true); // eslint-disable-line ember/jquery-ember-run
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -53,7 +56,8 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
@ -69,7 +73,8 @@ export default Component.extend({
|
|||
|
||||
meta.set('rawBody', body);
|
||||
|
||||
this.attrs.onAction(page, meta);
|
||||
let cb = this.get('onAction');
|
||||
cb(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
/*global Trello*/
|
||||
import $ from 'jquery';
|
||||
|
||||
import { htmlSafe } from '@ember/string';
|
||||
import { computed, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
@ -25,11 +24,9 @@ export default Component.extend(SectionMixin, NotifierMixin, TooltipMixin, {
|
|||
isDirty: false,
|
||||
busy: false,
|
||||
authenticated: false,
|
||||
config: {},
|
||||
boards: null,
|
||||
noBoards: false,
|
||||
appKey: "",
|
||||
|
||||
boardStyle: computed('config.board', function () {
|
||||
let board = this.get('config.board');
|
||||
|
||||
|
@ -41,6 +38,11 @@ export default Component.extend(SectionMixin, NotifierMixin, TooltipMixin, {
|
|||
return htmlSafe("background-color: " + color);
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.config = {};
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
let page = this.get('page');
|
||||
let config = {};
|
||||
|
@ -219,7 +221,8 @@ export default Component.extend(SectionMixin, NotifierMixin, TooltipMixin, {
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { computed, set } from '@ember/object';
|
||||
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
|
@ -31,8 +31,8 @@ export default Component.extend({
|
|||
let options = {
|
||||
selector: "#" + this.get('editorId'),
|
||||
relative_urls: false,
|
||||
cache_suffix: "?v=443",
|
||||
browser_spellcheck: false,
|
||||
cache_suffix: "?v=475",
|
||||
browser_spellcheck: true,
|
||||
gecko_spellcheck: false,
|
||||
theme: "modern",
|
||||
skin: 'documize',
|
||||
|
@ -116,7 +116,8 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
onCancel() {
|
||||
this.attrs.onCancel();
|
||||
let cb = this.get('onCancel');
|
||||
cb();
|
||||
},
|
||||
|
||||
onAction(title) {
|
||||
|
@ -127,7 +128,8 @@ export default Component.extend({
|
|||
page.set('title', title);
|
||||
meta.set('rawBody', editor.getContent());
|
||||
|
||||
this.attrs.onAction(page, meta);
|
||||
let cb = this.get('onAction');
|
||||
cb(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,13 +16,17 @@ import NotifierMixin from '../../mixins/notifier';
|
|||
import AuthMixin from '../../mixins/auth';
|
||||
|
||||
export default Component.extend(TooltipMixin, NotifierMixin, AuthMixin, {
|
||||
publicFolders: [],
|
||||
protectedFolders: [],
|
||||
privateFolders: [],
|
||||
hasPublicFolders: false,
|
||||
hasProtectedFolders: false,
|
||||
hasPrivateFolders: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.publicFolders = [];
|
||||
this.protectedFolders = [];
|
||||
this.privateFolders = [];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
let folders = this.get('spaces');
|
||||
let publicFolders = [];
|
||||
|
|
|
@ -9,29 +9,36 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import Component from '@ember/component';
|
||||
import $ from 'jquery';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
import AuthMixin from '../../mixins/auth';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
import ModalMixin from '../../mixins/modal';
|
||||
|
||||
export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
||||
spaceService: service('folder'),
|
||||
userSvc: service('user'),
|
||||
store: service(),
|
||||
spaceSvc: service('folder'),
|
||||
session: service(),
|
||||
appMeta: service(),
|
||||
pinned: service(),
|
||||
pinState : {
|
||||
isPinned: false,
|
||||
pinId: '',
|
||||
newName: ''
|
||||
},
|
||||
saveTemplate: {
|
||||
name: '',
|
||||
description: ''
|
||||
},
|
||||
showTools: true, // show document related tools? favourite, delete, make template...
|
||||
showDocumentLink: false, // show link to document in breadcrumbs
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.pinState = {
|
||||
isPinned: false,
|
||||
pinId: '',
|
||||
newName: ''
|
||||
};
|
||||
this.saveTemplate = {
|
||||
name: '',
|
||||
description: ''
|
||||
};
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
|
@ -50,7 +57,6 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.modalInputFocus('#document-template-modal', '#new-template-name');
|
||||
},
|
||||
|
||||
|
@ -62,8 +68,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
actions: {
|
||||
onDocumentDelete() {
|
||||
this.modalClose('#document-delete-modal');
|
||||
|
||||
this.attrs.onDocumentDelete();
|
||||
|
||||
let cb = this.get('onDocumentDelete');
|
||||
cb();
|
||||
},
|
||||
|
||||
onPrintDocument() {
|
||||
|
@ -118,7 +125,8 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
this.set('saveTemplate.name', '');
|
||||
this.set('saveTemplate.description', '');
|
||||
|
||||
this.attrs.onSaveTemplate(name, excerpt);
|
||||
let cb = this.get('onSaveTemplate');
|
||||
cb(name, excerpt);
|
||||
|
||||
this.modalClose('#document-template-modal');
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { schedule } from '@ember/runloop';
|
||||
|
@ -29,12 +30,7 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
copyTemplate: true,
|
||||
copyPermission: true,
|
||||
copyDocument: false,
|
||||
clonedSpace: { id: '' },
|
||||
pinState : {
|
||||
isPinned: false,
|
||||
pinId: '',
|
||||
newName: ''
|
||||
},
|
||||
|
||||
spaceSettings: computed('permissions', function() {
|
||||
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
|
||||
}),
|
||||
|
@ -49,10 +45,21 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
templateDocName: '',
|
||||
templateDocNameError: false,
|
||||
selectedTemplate: '',
|
||||
importedDocuments: [],
|
||||
importStatus: [],
|
||||
|
||||
dropzone: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.importedDocuments = [];
|
||||
this.importStatus = [];
|
||||
this.clonedSpace = { id: '' };
|
||||
this.pinState = {
|
||||
isPinned: false,
|
||||
pinId: '',
|
||||
newName: ''
|
||||
};
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
|
@ -226,8 +233,8 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
this.set('deleteSpaceName', '');
|
||||
$("#delete-space-name").removeClass("is-invalid");
|
||||
|
||||
this.attrs.onDeleteSpace(this.get('space.id'));
|
||||
|
||||
let cb = this.get('onDeleteSpace');
|
||||
cb(this.get('space.id'));
|
||||
|
||||
this.modalClose('#space-delete-modal');
|
||||
},
|
||||
|
@ -258,17 +265,17 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
|
||||
onShowTemplateDocModal() {
|
||||
let t = this.get('templates');
|
||||
if (t.length > 0) {
|
||||
t[0].set('selected', true);
|
||||
this.modalOpen("#template-doc-modal", {"show": true}, '#template-doc-name');
|
||||
}
|
||||
t.forEach((t) => {
|
||||
t.set('selected', false);
|
||||
});
|
||||
this.modalOpen("#template-doc-modal", {"show": true}, '#template-doc-name');
|
||||
},
|
||||
|
||||
onSelectTemplate(i) {
|
||||
let t = this.get('templates');
|
||||
t.forEach((t) => {
|
||||
t.set('selected', false);
|
||||
})
|
||||
});
|
||||
i.set('selected', true);
|
||||
this.set('selectedTemplate', i.id);
|
||||
},
|
||||
|
@ -281,16 +288,17 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
this.set('templateDocNameError', true);
|
||||
$('#template-doc-name').focus();
|
||||
return;
|
||||
} else {
|
||||
this.set('templateDocNameError', false);
|
||||
this.set('templateDocName', '');
|
||||
}
|
||||
|
||||
let id = this.get('selectedTemplate');
|
||||
if (is.empty(id)) {
|
||||
$('#widget-list-picker').addClass('is-invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('templateDocNameError', false);
|
||||
this.set('templateDocName', '');
|
||||
|
||||
this.modalClose("#template-doc-modal");
|
||||
|
||||
this.get('templateService').importSavedTemplate(this.get('space.id'), id, docName).then((document) => {
|
||||
|
@ -330,8 +338,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
this.set('importedDocuments', documents);
|
||||
|
||||
if (documents.length === 0) {
|
||||
this.modalClose("#import-doc-modal");
|
||||
this.attrs.onRefresh();
|
||||
this.modalClose("#import-doc-modal");
|
||||
let cb = this.get('onRefresh');
|
||||
cb();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import Component from '@ember/component';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { notEmpty } from '@ember/object/computed';
|
||||
|
@ -20,8 +21,13 @@ export default Component.extend(NotifierMixin, AuthMixin, {
|
|||
copyTemplate: true,
|
||||
copyPermission: true,
|
||||
copyDocument: false,
|
||||
clonedSpace: { id: '' },
|
||||
hasClone: notEmpty('clonedSpace.id'),
|
||||
clonedSpace: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
// this.clonedSpace = { id: '' };
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
@ -58,13 +64,13 @@ export default Component.extend(NotifierMixin, AuthMixin, {
|
|||
}
|
||||
|
||||
this.set('spaceName', '');
|
||||
this.set('clonedSpace.id', '');
|
||||
this.set('clonedSpace', null);
|
||||
$("#new-space-name").removeClass("is-invalid");
|
||||
|
||||
$('#add-space-modal').modal('hide');
|
||||
$('#add-space-modal').modal('dispose');
|
||||
|
||||
this.attrs.onAddSpace(payload);
|
||||
let cb = this.get('onAddSpace');
|
||||
cb(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -21,13 +21,13 @@ export default Component.extend({
|
|||
store: service(),
|
||||
pinned: service(),
|
||||
enableLogout: true,
|
||||
pins: [],
|
||||
hasPins: notEmpty('pins'),
|
||||
hasSpacePins: notEmpty('spacePins'),
|
||||
hasDocumentPins: notEmpty('documentPins'),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.pins = [];
|
||||
|
||||
if (this.get('appMeta.authProvider') === constants.AuthProvider.Keycloak) {
|
||||
let config = this.get('appMeta.authConfig');
|
||||
|
|
|
@ -15,7 +15,6 @@ import Component from '@ember/component';
|
|||
|
||||
export default Component.extend({
|
||||
cssClass: "",
|
||||
content: [],
|
||||
prompt: null,
|
||||
optionValuePath: 'id',
|
||||
optionLabelPath: 'name',
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import Component from '@ember/component';
|
||||
import stringUtil from '../../utils/string';
|
||||
|
||||
|
@ -56,7 +57,8 @@ export default Component.extend({
|
|||
return;
|
||||
}
|
||||
|
||||
let result = this.attrs.onAction();
|
||||
let cb = this.get('onAction');
|
||||
let result = cb();
|
||||
if (result) {
|
||||
this.set('show', false);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import { computed } from '@ember/object';
|
|||
export default Component.extend({
|
||||
nameField: 'category',
|
||||
singleSelect: false,
|
||||
items: [],
|
||||
maxHeight: 0,
|
||||
onSelect: null,
|
||||
styleCss: computed('maxHeight', function () {
|
||||
|
@ -29,13 +28,18 @@ export default Component.extend({
|
|||
}
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
actions: {
|
||||
onToggle(item) {
|
||||
// callback takes precedence
|
||||
// caller sets item to 'selected'
|
||||
let cb = this.get('onSelect');
|
||||
if (cb !== null) {
|
||||
this.attrs.onSelect(item);
|
||||
let cb = this.get('onSelect');
|
||||
cb(item);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ export default Component.extend({
|
|||
actions: {
|
||||
onCheck() {
|
||||
if (this.get('onClick') !== null) {
|
||||
this.attrs.onClick(this.get('value'));
|
||||
let cb = this.get('onClick');
|
||||
cb(this.get('value'));
|
||||
} else {
|
||||
this.set('selected', !this.get('selected'));
|
||||
}
|
||||
|
|
|
@ -10,12 +10,13 @@
|
|||
// https://documize.com
|
||||
|
||||
import { run } from '@ember/runloop';
|
||||
|
||||
import Component from '@ember/component';
|
||||
import miscUtil from '../utils/misc';
|
||||
|
||||
export default Component.extend({
|
||||
notifications: [],
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this.eventBus.subscribe('notifyUser', this, 'showNotification');
|
||||
|
|
|
@ -9,15 +9,14 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { empty } from '@ember/object/computed';
|
||||
|
||||
import Component from '@ember/component';
|
||||
import { computed, set } from '@ember/object';
|
||||
import { isPresent, isEqual, isEmpty } from '@ember/utils';
|
||||
import AuthProvider from '../mixins/auth';
|
||||
|
||||
export default Component.extend(AuthProvider, {
|
||||
password: { password: "", confirmation: "" },
|
||||
hasFirstnameError: empty('model.firstname'),
|
||||
hasLastnameError: empty('model.lastname'),
|
||||
hasEmailError: computed('model.email', function() {
|
||||
|
@ -45,6 +44,11 @@ export default Component.extend(AuthProvider, {
|
|||
}
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.password = { password: "", confirmation: "" };
|
||||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
let password = this.get('password.password');
|
||||
|
|
54
gui/app/constants/constants.js
Normal file
54
gui/app/constants/constants.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
// 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 EmberObject from "@ember/object";
|
||||
|
||||
let constants = EmberObject.extend({
|
||||
// Document
|
||||
ProtectionType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
||||
None: 0,
|
||||
Lock: 1,
|
||||
Review: 2,
|
||||
|
||||
NoneLabel: 'Changes permitted without approval',
|
||||
LockLabel: 'Locked, changes not permitted',
|
||||
ReviewLabel: 'Changes require approval before publication'
|
||||
},
|
||||
|
||||
// Document
|
||||
ApprovalType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
||||
None: 0,
|
||||
Anybody: 1,
|
||||
Majority: 2,
|
||||
Unanimous: 3,
|
||||
|
||||
AnybodyLabel: 'Approval required from any approver',
|
||||
MajorityLabel: 'Majority approval required from approvers',
|
||||
UnanimousLabel: 'Unanimous approval required from all approvers'
|
||||
},
|
||||
|
||||
// Section
|
||||
ChangeState: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
||||
Published: 0,
|
||||
Pending: 1,
|
||||
UnderReview: 2,
|
||||
Rejected: 3,
|
||||
PendingNew: 4,
|
||||
},
|
||||
|
||||
// Section
|
||||
PageType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
||||
Tab: 'tab',
|
||||
Section: 'section'
|
||||
}
|
||||
});
|
||||
|
||||
export default { constants }
|
26
gui/app/constants/econstants.js
Normal file
26
gui/app/constants/econstants.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
// 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 EmberObject from "@ember/object";
|
||||
|
||||
let econstants = EmberObject.extend({
|
||||
// Document
|
||||
ActionType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
||||
Read: 1,
|
||||
Feedback: 2,
|
||||
Contribute: 3,
|
||||
ApprovalRequest: 4,
|
||||
Approved: 5,
|
||||
Rejected: 6,
|
||||
},
|
||||
});
|
||||
|
||||
export default { econstants }
|
27
gui/app/helpers/is-one-of.js
Normal file
27
gui/app/helpers/is-one-of.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 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 { helper } from '@ember/component/helper';
|
||||
|
||||
export function isOneOf(params/*, hash*/) {
|
||||
if (is.not.undefined(params) || is.not.null(params)) {
|
||||
if (params.length >= 2) {
|
||||
let value = params[0];
|
||||
for (let i=1; i < params.length; i++) {
|
||||
if (params[i] == value) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default helper(isOneOf);
|
|
@ -9,7 +9,11 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
export function initialize( /*application*/ ) {
|
||||
import $ from 'jquery';
|
||||
import constants from '../constants/constants';
|
||||
import econstants from '../constants/econstants';
|
||||
|
||||
export function initialize(application) {
|
||||
// address insecure jquery defaults (kudos: @nathanhammond)
|
||||
$.globalEval = function() {};
|
||||
$.ajaxSetup({
|
||||
|
@ -19,6 +23,11 @@ export function initialize( /*application*/ ) {
|
|||
}
|
||||
});
|
||||
|
||||
let cs = constants.constants;
|
||||
let ec = econstants.econstants;
|
||||
application.register('constants:main', cs);
|
||||
application.register('econstants:main', ec);
|
||||
|
||||
Dropzone.autoDiscover = false;
|
||||
CodeMirror.modeURL = "/codemirror/mode/%N/%N.js";
|
||||
}
|
||||
|
|
25
gui/app/initializers/constants.js
Normal file
25
gui/app/initializers/constants.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
// 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
|
||||
|
||||
export function initialize(application) {
|
||||
application.inject('route', 'constants', 'constants:main');
|
||||
application.inject('controller', 'constants', 'constants:main');
|
||||
application.inject('component', 'constants', 'constants:main');
|
||||
application.inject('template', 'constants', 'constants:main');
|
||||
application.inject('service', 'constants', 'constants:main');
|
||||
application.inject('model', 'constants', 'constants:main');
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'constants',
|
||||
after: "application",
|
||||
initialize: initialize
|
||||
};
|
24
gui/app/initializers/econstants.js
Normal file
24
gui/app/initializers/econstants.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
// 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
|
||||
|
||||
export function initialize(application) {
|
||||
application.inject('route', 'econstants', 'econstants:main');
|
||||
application.inject('controller', 'econstants', 'econstants:main');
|
||||
application.inject('component', 'econstants', 'econstants:main');
|
||||
application.inject('template', 'econstants', 'econstants:main');
|
||||
application.inject('service', 'econstants', 'econstants:main');
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'econstants',
|
||||
after: "application",
|
||||
initialize: initialize
|
||||
};
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import Mixin from '@ember/object/mixin';
|
||||
|
||||
// ID values expected format:
|
||||
|
|
|
@ -12,15 +12,15 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
|
||||
export default Mixin.create({
|
||||
isReadonly: function () {
|
||||
if (this.get('page.userId') === this.get('session.session.authenticated.user.id')) {
|
||||
return undefined;
|
||||
} else {
|
||||
return "readonly";
|
||||
}
|
||||
}.property('page'),
|
||||
// isReadonly() {
|
||||
// if (this.get('page.userId') === this.get('session.session.authenticated.user.id')) {
|
||||
// return undefined;
|
||||
// } else {
|
||||
// return "readonly";
|
||||
// }
|
||||
// }.property('page'),
|
||||
|
||||
isMine: function () {
|
||||
return this.get('page.userId') === this.get('session.session.authenticated.user.id');
|
||||
}.property('page')
|
||||
// isMine() {
|
||||
// return this.get('page.userId') === this.get('session.session.authenticated.user.id');
|
||||
// }.property('page')
|
||||
});
|
|
@ -9,12 +9,11 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import { schedule } from '@ember/runloop';
|
||||
|
||||
export default Mixin.create({
|
||||
tooltips: [],
|
||||
|
||||
renderTooltips() {
|
||||
schedule('afterRender', () => {
|
||||
$('[data-toggle="tooltip"]').tooltip('dispose');
|
||||
|
@ -24,5 +23,17 @@ export default Mixin.create({
|
|||
|
||||
removeTooltips() {
|
||||
$('[data-toggle="tooltip"]').tooltip('dispose');
|
||||
},
|
||||
|
||||
renderPopovers() {
|
||||
schedule('afterRender', () => {
|
||||
$('[data-toggle="popover"]').popover('dispose');
|
||||
$('body').popover({selector: '[data-toggle="popover"]', delay: 250});
|
||||
});
|
||||
},
|
||||
|
||||
removePopovers() {
|
||||
$('[data-toggle="tooltip"]').popover('dispose');
|
||||
}
|
||||
|
||||
});
|
||||
|
|
28
gui/app/models/doc-search-result.js
Normal file
28
gui/app/models/doc-search-result.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
// 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 Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export default Model.extend({
|
||||
orgId: attr(),
|
||||
document: attr(),
|
||||
documentId: attr(),
|
||||
documentSlug: attr(),
|
||||
tags: attr(),
|
||||
excerpt: attr(),
|
||||
itemId: attr(),
|
||||
itemType: attr(),
|
||||
space: attr(),
|
||||
spaceId: attr(),
|
||||
spaceSlug: attr(),
|
||||
selected: attr()
|
||||
});
|
|
@ -18,6 +18,8 @@ export default Model.extend({
|
|||
orgId: attr('string'),
|
||||
folderId: attr('string'),
|
||||
documentId: attr('string'),
|
||||
pageId: attr('string'),
|
||||
pageTitle: attr('string'),
|
||||
userId: attr('string'),
|
||||
firstname: attr('string'),
|
||||
lastname: attr('string'),
|
||||
|
@ -55,6 +57,9 @@ export default Model.extend({
|
|||
case constants.UserActivityType.PublishedBlock:
|
||||
label = 'Published Block';
|
||||
break;
|
||||
case constants.UserActivityType.Rejected:
|
||||
label = 'Rejected';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -93,11 +98,13 @@ export default Model.extend({
|
|||
case constants.UserActivityType.PublishedBlock:
|
||||
color = 'color-blue';
|
||||
break;
|
||||
case constants.UserActivityType.Rejected:
|
||||
color = 'color-red';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return color;
|
||||
})
|
||||
|
||||
});
|
||||
|
|
25
gui/app/models/document-role.js
Normal file
25
gui/app/models/document-role.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
// 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 Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export default Model.extend({
|
||||
orgId: attr('string'),
|
||||
documentId: attr('string'),
|
||||
userId: attr('string'),
|
||||
fullname: attr('string'), // client-side usage only, not from API
|
||||
|
||||
documentEdit: attr('boolean'), // space level setting
|
||||
documentApprove: attr('boolean'), // space level setting
|
||||
documentRoleEdit: attr('boolean'), // document level setting
|
||||
documentRoleApprove: attr('boolean') // document level setting
|
||||
});
|
|
@ -25,7 +25,8 @@ export default Model.extend({
|
|||
userId: attr('string'),
|
||||
tags: attr('string'),
|
||||
template: attr('boolean'),
|
||||
layout: attr('string'),
|
||||
protection: attr('number', { defaultValue: 0 }),
|
||||
approval: attr('number', { defaultValue: 0 }),
|
||||
|
||||
// client-side property
|
||||
selected: attr('boolean', { defaultValue: false }),
|
||||
|
|
30
gui/app/models/page-container.js
Normal file
30
gui/app/models/page-container.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// 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 Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
// import { belongsTo, hasMany } from 'ember-data/relationships';
|
||||
|
||||
export default Model.extend({
|
||||
// page: belongsTo('page', { inverse: null }),
|
||||
// meta: belongsTo('page-meta', { inverse: null }),
|
||||
// pending: hasMany('page-pending', { inverse: null }),
|
||||
page: attr(),
|
||||
meta: attr(),
|
||||
pending: attr(),
|
||||
changePending: attr('boolean'),
|
||||
changeAwaitingReview: attr('boolean'),
|
||||
changeRejected: attr('boolean'),
|
||||
userHasChangePending: attr('boolean'),
|
||||
userHasChangeAwaitingReview: attr('boolean'),
|
||||
userHasChangeRejected: attr('boolean'),
|
||||
userHasNewPagePending: attr('boolean')
|
||||
});
|
25
gui/app/models/page-pending.js
Normal file
25
gui/app/models/page-pending.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
// 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 Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export default Model.extend({
|
||||
page: attr(),
|
||||
meta: attr(),
|
||||
owner: attr('string'),
|
||||
changePending: attr('boolean'),
|
||||
changeAwaitingReview: attr('boolean'),
|
||||
changeRejected: attr('boolean'),
|
||||
userHasChangePending: attr('boolean'),
|
||||
userHasChangeAwaitingReview: attr('boolean'),
|
||||
userHasChangeRejected: attr('boolean')
|
||||
});
|
|
@ -12,7 +12,6 @@
|
|||
import { computed } from '@ember/object';
|
||||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
// import { hasMany } from 'ember-data/relationships';
|
||||
|
||||
export default Model.extend({
|
||||
documentId: attr('string'),
|
||||
|
@ -20,7 +19,7 @@ export default Model.extend({
|
|||
contentType: attr('string'),
|
||||
pageType: attr('string'),
|
||||
level: attr('number', { defaultValue: 1 }),
|
||||
sequence: attr('number', { defaultValue: 0 }),
|
||||
sequence: attr('number', { defaultValue: 1024 }),
|
||||
numbering: attr('string'),
|
||||
revisions: attr('number', { defaultValue: 0 }),
|
||||
blockId: attr('string'),
|
||||
|
@ -28,10 +27,12 @@ export default Model.extend({
|
|||
body: attr('string'),
|
||||
rawBody: attr('string'),
|
||||
meta: attr(),
|
||||
|
||||
status: attr('number', { defaultValue: 0 }),
|
||||
relativeId: attr('string'),
|
||||
userId: attr('string'),
|
||||
|
||||
tagName: computed('level', function () {
|
||||
return "h2";
|
||||
// return "h" + (this.get('level') + 1);
|
||||
}),
|
||||
|
||||
tocIndent: computed('level', function () {
|
||||
|
@ -48,5 +49,16 @@ export default Model.extend({
|
|||
}),
|
||||
|
||||
created: attr(),
|
||||
revised: attr()
|
||||
revised: attr(),
|
||||
|
||||
// is this a new page that is pending and belongs to the user?
|
||||
isNewPageUserPending(userId) {
|
||||
return this.get('relativeId') === '' && this.get('userId') === userId && (
|
||||
this.get('status') === this.get('constants').ChangeState.PendingNew || this.get('status') === this.get('constants').ChangeState.UnderReview);
|
||||
},
|
||||
|
||||
// is this new page ready for review?
|
||||
isNewPageReviewReady() {
|
||||
return this.get('relativeId') === '' && this.get('status') === this.get('constants').ChangeState.UnderReview;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -27,5 +27,6 @@ export default Model.extend({
|
|||
documentDelete: attr('boolean'),
|
||||
documentMove: attr('boolean'),
|
||||
documentCopy: attr('boolean'),
|
||||
documentTemplate: attr('boolean')
|
||||
documentTemplate: attr('boolean'),
|
||||
documentApprove: attr('boolean')
|
||||
});
|
||||
|
|
|
@ -15,10 +15,13 @@ import attr from 'ember-data/attr';
|
|||
|
||||
export default Model.extend({
|
||||
documentName: attr('string'),
|
||||
documentId: attr('string'),
|
||||
folderId: attr('string'),
|
||||
contributed: attr('string'),
|
||||
viewed: attr('string'),
|
||||
created: attr('string'),
|
||||
approved: attr('string'),
|
||||
rejected: attr('string'),
|
||||
|
||||
hasContributed: computed('contributed', function () {
|
||||
return this.get('contributed').length > 0;
|
||||
|
@ -28,5 +31,11 @@ export default Model.extend({
|
|||
}),
|
||||
hasCreated: computed('created', function () {
|
||||
return this.get('created').length > 0;
|
||||
})
|
||||
}),
|
||||
hasApproved: computed('approved', function () {
|
||||
return this.get('approved').length > 0;
|
||||
}),
|
||||
hasRejected: computed('rejected', function () {
|
||||
return this.get('rejected').length > 0;
|
||||
})
|
||||
});
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import $ from 'jquery';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import constants from '../../../utils/constants';
|
||||
|
||||
|
@ -29,6 +29,7 @@ export default Route.extend({
|
|||
},
|
||||
|
||||
activate() {
|
||||
this.get('browser').setTitleAsPhrase('Forgot Password');
|
||||
$('body').addClass('background-color-theme-light');
|
||||
},
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue