mirror of
https://github.com/documize/community.git
synced 2025-07-18 20:59:43 +02:00
parent
2b66d0096a
commit
e014f5b5c1
18 changed files with 541 additions and 88 deletions
|
@ -29,16 +29,16 @@ type Store struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordUserActivity logs user initiated data changes.
|
// RecordUserActivity logs user initiated data changes.
|
||||||
func (s Store) RecordUserActivity(ctx domain.RequestContext, activity activity.UserActivity) (err error) {
|
func (s Store) RecordUserActivity(ctx domain.RequestContext, activity activity.UserActivity) {
|
||||||
activity.OrgID = ctx.OrgID
|
activity.OrgID = ctx.OrgID
|
||||||
activity.UserID = ctx.UserID
|
activity.UserID = ctx.UserID
|
||||||
activity.Created = time.Now().UTC()
|
activity.Created = time.Now().UTC()
|
||||||
|
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_user_activity (c_orgid, c_userid, c_spaceid, c_docid, c_sectionid, c_sourcetype, c_activitytype, c_metadata, c_created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
_, err := ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_user_activity (c_orgid, c_userid, c_spaceid, c_docid, c_sectionid, c_sourcetype, c_activitytype, c_metadata, c_created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
||||||
activity.OrgID, activity.UserID, activity.SpaceID, activity.DocumentID, activity.SectionID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created)
|
activity.OrgID, activity.UserID, activity.SpaceID, activity.DocumentID, activity.SectionID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "execute record user activity")
|
s.Runtime.Log.Error("execute record user activity", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -682,7 +682,7 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) {
|
||||||
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_doc`+w)
|
FROM dmz_doc`+w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "select.document")
|
return errors.Wrap(err, "select.document")
|
||||||
|
|
|
@ -14,6 +14,7 @@ package document
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -88,18 +89,12 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: document.SpaceID,
|
SpaceID: document.SpaceID,
|
||||||
DocumentID: document.RefID,
|
DocumentID: document.RefID,
|
||||||
SourceType: activity.SourceTypeDocument,
|
SourceType: activity.SourceTypeDocument,
|
||||||
ActivityType: activity.TypeRead})
|
ActivityType: activity.TypeRead})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,10 +185,26 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort document list by title.
|
sortedDocs := doc.SortedDocs{}
|
||||||
sort.Sort(doc.ByName(filtered))
|
|
||||||
|
|
||||||
response.WriteJSON(w, filtered)
|
for j := range filtered {
|
||||||
|
if filtered[j].Sequence == doc.Unsequenced {
|
||||||
|
sortedDocs.Unpinned = append(sortedDocs.Unpinned, filtered[j])
|
||||||
|
} else {
|
||||||
|
sortedDocs.Pinned = append(sortedDocs.Pinned, filtered[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort document list by title.
|
||||||
|
sort.Sort(doc.ByName(sortedDocs.Unpinned))
|
||||||
|
|
||||||
|
// Sort document list by sequence.
|
||||||
|
sort.Sort(doc.BySeq(sortedDocs.Pinned))
|
||||||
|
|
||||||
|
final := sortedDocs.Pinned
|
||||||
|
final = append(final, sortedDocs.Unpinned...)
|
||||||
|
|
||||||
|
response.WriteJSON(w, final)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates an existing document using the format described
|
// Update updates an existing document using the format described
|
||||||
|
@ -249,7 +260,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldDoc.SpaceID != d.SpaceID {
|
if oldDoc.SpaceID != d.SpaceID {
|
||||||
h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
|
_, _ = h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
|
||||||
err = h.Store.Document.MoveActivity(ctx, documentID, oldDoc.SpaceID, d.SpaceID)
|
err = h.Store.Document.MoveActivity(ctx, documentID, oldDoc.SpaceID, d.SpaceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Runtime.Rollback(ctx.Transaction)
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
@ -312,9 +323,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
h.Runtime.Commit(ctx.Transaction)
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
h.Store.Space.SetStats(ctx, d.SpaceID)
|
_ = h.Store.Space.SetStats(ctx, d.SpaceID)
|
||||||
if oldDoc.SpaceID != d.SpaceID {
|
if oldDoc.SpaceID != d.SpaceID {
|
||||||
h.Store.Space.SetStats(ctx, oldDoc.SpaceID)
|
_ = h.Store.Space.SetStats(ctx, oldDoc.SpaceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate)
|
h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate)
|
||||||
|
@ -480,18 +491,13 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: "",
|
SpaceID: "",
|
||||||
DocumentID: "",
|
DocumentID: "",
|
||||||
Metadata: options.Keywords,
|
Metadata: options.Keywords,
|
||||||
SourceType: activity.SourceTypeSearch,
|
SourceType: activity.SourceTypeSearch,
|
||||||
ActivityType: activity.TypeSearched})
|
ActivityType: activity.TypeSearched})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -526,18 +532,13 @@ func (h *Handler) recordSearchActivity(ctx domain.RequestContext, q []search.Que
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, isExisting := prev[q[i].DocumentID]; !isExisting {
|
if _, isExisting := prev[q[i].DocumentID]; !isExisting {
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: q[i].SpaceID,
|
SpaceID: q[i].SpaceID,
|
||||||
DocumentID: q[i].DocumentID,
|
DocumentID: q[i].DocumentID,
|
||||||
Metadata: keywords,
|
Metadata: keywords,
|
||||||
SourceType: activity.SourceTypeSearch,
|
SourceType: activity.SourceTypeSearch,
|
||||||
ActivityType: activity.TypeSearched})
|
ActivityType: activity.TypeSearched})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
prev[q[i].DocumentID] = true
|
prev[q[i].DocumentID] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -713,16 +714,11 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if document.Lifecycle == workflow.LifecycleLive {
|
if document.Lifecycle == workflow.LifecycleLive {
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: document.SpaceID,
|
SpaceID: document.SpaceID,
|
||||||
DocumentID: document.RefID,
|
DocumentID: document.RefID,
|
||||||
SourceType: activity.SourceTypeDocument,
|
SourceType: activity.SourceTypeDocument,
|
||||||
ActivityType: activity.TypeRead})
|
ActivityType: activity.TypeRead})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
@ -786,7 +782,7 @@ func (h *Handler) Export(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(export))
|
_, _ = w.Write([]byte(export))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplicate makes a copy of a document.
|
// Duplicate makes a copy of a document.
|
||||||
|
@ -1014,3 +1010,219 @@ func (h *Handler) Duplicate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
response.WriteEmpty(w)
|
response.WriteEmpty(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pin marks existing document with sequence number so that it
|
||||||
|
// appears at the top-most space view.
|
||||||
|
func (h *Handler) Pin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "document.Pin"
|
||||||
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
|
documentID := request.Param(r, "documentID")
|
||||||
|
if len(documentID) == 0 {
|
||||||
|
response.WriteMissingDataError(w, method, "documentID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
response.WriteServerError(w, method, errors.New("unable to start transaction"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := h.Store.Document.Get(ctx, documentID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the next sequence number for this newly pinned document.
|
||||||
|
seq, err := h.Store.Document.PinSequence(ctx, d.SpaceID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.Store.Document.Pin(ctx, documentID, seq+1)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
|
SpaceID: d.SpaceID,
|
||||||
|
DocumentID: documentID,
|
||||||
|
SourceType: activity.SourceTypeDocument,
|
||||||
|
ActivityType: activity.TypePinned})
|
||||||
|
|
||||||
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
|
response.WriteEmpty(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpin removes an existing document from the space pinned list.
|
||||||
|
func (h *Handler) Unpin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "document.Unpin"
|
||||||
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
|
documentID := request.Param(r, "documentID")
|
||||||
|
if len(documentID) == 0 {
|
||||||
|
response.WriteMissingDataError(w, method, "documentID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
response.WriteServerError(w, method, errors.New("unable to start transaction"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := h.Store.Document.Get(ctx, documentID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.Store.Document.Unpin(ctx, documentID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
|
SpaceID: d.SpaceID,
|
||||||
|
DocumentID: documentID,
|
||||||
|
SourceType: activity.SourceTypeDocument,
|
||||||
|
ActivityType: activity.TypeUnpinned})
|
||||||
|
|
||||||
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
|
response.WriteEmpty(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PinMove moves pinned document up or down in the sequence.
|
||||||
|
func (h *Handler) PinMove(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "document.PinMove"
|
||||||
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
|
documentID := request.Param(r, "documentID")
|
||||||
|
if len(documentID) == 0 {
|
||||||
|
response.WriteMissingDataError(w, method, "documentID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
direction := request.Query(r, "direction")
|
||||||
|
if len(direction) == 0 {
|
||||||
|
response.WriteMissingDataError(w, method, "direction")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
response.WriteServerError(w, method, errors.New("unable to start transaction"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := h.Store.Document.Get(ctx, documentID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all pinned documents in the space.
|
||||||
|
pinnedDocs, err := h.Store.Document.Pinned(ctx, d.SpaceID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort document list by sequence.
|
||||||
|
sort.Sort(doc.BySeq(pinnedDocs))
|
||||||
|
|
||||||
|
// Resequence the documents.
|
||||||
|
for i := range pinnedDocs {
|
||||||
|
if pinnedDocs[i].RefID == documentID {
|
||||||
|
if direction == "u" {
|
||||||
|
if i-1 >= 0 {
|
||||||
|
me := pinnedDocs[i].Sequence
|
||||||
|
target := pinnedDocs[i-1].Sequence
|
||||||
|
|
||||||
|
pinnedDocs[i-1].Sequence = me
|
||||||
|
pinnedDocs[i].Sequence = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if direction == "d" {
|
||||||
|
if i+1 < len(pinnedDocs) {
|
||||||
|
me := pinnedDocs[i].Sequence
|
||||||
|
target := pinnedDocs[i+1].Sequence
|
||||||
|
|
||||||
|
pinnedDocs[i+1].Sequence = me
|
||||||
|
pinnedDocs[i].Sequence = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort document list by sequence.
|
||||||
|
sort.Sort(doc.BySeq(pinnedDocs))
|
||||||
|
|
||||||
|
// Save the resequenced documents.
|
||||||
|
for i := range pinnedDocs {
|
||||||
|
err = h.Store.Document.Pin(ctx, pinnedDocs[i].RefID, i+1)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
|
SpaceID: d.SpaceID,
|
||||||
|
DocumentID: documentID,
|
||||||
|
SourceType: activity.SourceTypeDocument,
|
||||||
|
ActivityType: activity.TypePinSequence})
|
||||||
|
|
||||||
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
|
response.WriteEmpty(w)
|
||||||
|
}
|
||||||
|
|
|
@ -16,10 +16,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
"github.com/documize/community/domain/store"
|
"github.com/documize/community/domain/store"
|
||||||
"github.com/documize/community/model/doc"
|
"github.com/documize/community/model/doc"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Store provides data access to space category information.
|
// Store provides data access to space category information.
|
||||||
|
@ -36,10 +37,12 @@ func (s Store) Add(ctx domain.RequestContext, d doc.Document) (err error) {
|
||||||
|
|
||||||
_, err = ctx.Transaction.Exec(s.Bind(`
|
_, err = ctx.Transaction.Exec(s.Bind(`
|
||||||
INSERT INTO dmz_doc (c_refid, c_orgid, c_spaceid, c_userid, c_job, c_location, c_name, c_desc, c_slug, c_tags,
|
INSERT INTO dmz_doc (c_refid, c_orgid, c_spaceid, c_userid, c_job, c_location, c_name, c_desc, c_slug, c_tags,
|
||||||
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_groupid, c_created, c_revised)
|
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_seq, c_groupid,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
c_created, c_revised)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||||
d.RefID, d.OrgID, d.SpaceID, d.UserID, d.Job, d.Location, d.Name, d.Excerpt, d.Slug, d.Tags,
|
d.RefID, d.OrgID, d.SpaceID, d.UserID, d.Job, d.Location, d.Name, d.Excerpt, d.Slug, d.Tags,
|
||||||
d.Template, d.Protection, d.Approval, d.Lifecycle, d.Versioned, d.VersionID, d.VersionOrder, d.GroupID, d.Created, d.Revised)
|
d.Template, d.Protection, d.Approval, d.Lifecycle, d.Versioned, d.VersionID, d.VersionOrder, d.Sequence,
|
||||||
|
d.GroupID, d.Created, d.Revised)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "execute insert document")
|
err = errors.Wrap(err, "execute insert document")
|
||||||
|
@ -55,7 +58,7 @@ func (s Store) Get(ctx domain.RequestContext, id string) (document doc.Document,
|
||||||
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_doc
|
FROM dmz_doc
|
||||||
WHERE c_orgid=? AND c_refid=?`),
|
WHERE c_orgid=? AND c_refid=?`),
|
||||||
ctx.OrgID, id)
|
ctx.OrgID, id)
|
||||||
|
@ -78,7 +81,7 @@ func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
|
||||||
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_doc
|
FROM dmz_doc
|
||||||
WHERE c_orgid=? AND c_template=`+s.IsFalse()+` AND c_spaceid IN
|
WHERE c_orgid=? AND c_template=`+s.IsFalse()+` AND c_spaceid IN
|
||||||
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_refid IN
|
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_refid IN
|
||||||
|
@ -111,7 +114,7 @@ func (s Store) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu
|
||||||
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_doc
|
FROM dmz_doc
|
||||||
WHERE c_orgid=? AND c_spaceid=? AND c_template=`+s.IsTrue()+` AND c_lifecycle=1
|
WHERE c_orgid=? AND c_spaceid=? AND c_template=`+s.IsTrue()+` AND c_lifecycle=1
|
||||||
AND c_spaceid IN
|
AND c_spaceid IN
|
||||||
|
@ -167,7 +170,8 @@ func (s Store) Update(ctx domain.RequestContext, document doc.Document) (err err
|
||||||
c_spaceid=:spaceid, c_userid=:userid, c_job=:job, c_location=:location, c_name=:name,
|
c_spaceid=:spaceid, c_userid=:userid, c_job=:job, c_location=:location, c_name=:name,
|
||||||
c_desc=:excerpt, c_slug=:slug, c_tags=:tags, c_template=:template,
|
c_desc=:excerpt, c_slug=:slug, c_tags=:tags, c_template=:template,
|
||||||
c_protection=:protection, c_approval=:approval, c_lifecycle=:lifecycle,
|
c_protection=:protection, c_approval=:approval, c_lifecycle=:lifecycle,
|
||||||
c_versioned=:versioned, c_versionid=:versionid, c_versionorder=:versionorder,
|
c_versioned=:versioned, c_versionid=:versionid, c_versionorder=:versionorder,
|
||||||
|
c_seq=:sequence,
|
||||||
c_groupid=:groupid, c_revised=:revised
|
c_groupid=:groupid, c_revised=:revised
|
||||||
WHERE c_orgid=:orgid AND c_refid=:refid`),
|
WHERE c_orgid=:orgid AND c_refid=:refid`),
|
||||||
&document)
|
&document)
|
||||||
|
@ -331,3 +335,78 @@ func (s Store) GetVersions(ctx domain.RequestContext, groupID string) (v []doc.V
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pin allocates sequence number to specified document so that it appears
|
||||||
|
// at the documents list.
|
||||||
|
func (s Store) Pin(ctx domain.RequestContext, documentID string, seq int) (err error) {
|
||||||
|
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_doc SET c_seq=? WHERE c_orgid=? AND c_refid=?"),
|
||||||
|
seq, ctx.OrgID, documentID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "document.store.Pin")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpin resets sequence number for given document.
|
||||||
|
func (s Store) Unpin(ctx domain.RequestContext, documentID string) (err error) {
|
||||||
|
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_doc SET c_seq=? WHERE c_orgid=? AND c_refid=?"),
|
||||||
|
doc.Unsequenced, ctx.OrgID, documentID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "document.store.Unpin")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PinSequence fectches pinned documents and returns current
|
||||||
|
// maximum sequence value.
|
||||||
|
func (s Store) PinSequence(ctx domain.RequestContext, spaceID string) (max int, err error) {
|
||||||
|
max = 0
|
||||||
|
|
||||||
|
err = s.Runtime.Db.Get(&max, s.Bind(`
|
||||||
|
SELECT MAX(c_seq)
|
||||||
|
FROM dmz_doc
|
||||||
|
WHERE c_orgid=? AND c_spaceid=?
|
||||||
|
AND c_seq != 99999`),
|
||||||
|
ctx.OrgID, spaceID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
max = doc.Unsequenced
|
||||||
|
err = errors.Wrap(err, "document.store.PinSequence")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pinned documents for space are fetched.
|
||||||
|
func (s Store) Pinned(ctx domain.RequestContext, spaceID string) (d []doc.Document, err error) {
|
||||||
|
d = []doc.Document{}
|
||||||
|
|
||||||
|
err = s.Runtime.Db.Select(&d, s.Bind(`
|
||||||
|
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
|
||||||
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid,
|
||||||
|
c_created AS created, c_revised AS revised
|
||||||
|
FROM dmz_doc
|
||||||
|
WHERE c_orgid=? AND c_spaceid=?
|
||||||
|
AND c_seq != 99999
|
||||||
|
ORDER BY c_seq`),
|
||||||
|
ctx.OrgID, spaceID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "document.store.Pinned")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -1504,18 +1504,13 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
} else {
|
} else {
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: doc.SpaceID,
|
SpaceID: doc.SpaceID,
|
||||||
DocumentID: doc.RefID,
|
DocumentID: doc.RefID,
|
||||||
Metadata: source, // deliberate
|
Metadata: source, // deliberate
|
||||||
SourceType: activity.SourceTypeSearch, // deliberate
|
SourceType: activity.SourceTypeSearch, // deliberate
|
||||||
ActivityType: activity.TypeRead})
|
ActivityType: activity.TypeRead})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,14 +138,10 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: sp.RefID,
|
SpaceID: sp.RefID,
|
||||||
SourceType: activity.SourceTypeSpace,
|
SourceType: activity.SourceTypeSpace,
|
||||||
ActivityType: activity.TypeCreated})
|
ActivityType: activity.TypeCreated})
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
|
@ -444,16 +440,11 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: sp.RefID,
|
SpaceID: sp.RefID,
|
||||||
SourceType: activity.SourceTypeSpace,
|
SourceType: activity.SourceTypeSpace,
|
||||||
ActivityType: activity.TypeRead})
|
ActivityType: activity.TypeRead})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
response.WriteJSON(w, sp)
|
response.WriteJSON(w, sp)
|
||||||
|
@ -657,14 +648,10 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: id,
|
SpaceID: id,
|
||||||
SourceType: activity.SourceTypeSpace,
|
SourceType: activity.SourceTypeSpace,
|
||||||
ActivityType: activity.TypeDeleted})
|
ActivityType: activity.TypeDeleted})
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
|
@ -764,14 +751,10 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: id,
|
SpaceID: id,
|
||||||
SourceType: activity.SourceTypeSpace,
|
SourceType: activity.SourceTypeSpace,
|
||||||
ActivityType: activity.TypeDeleted})
|
ActivityType: activity.TypeDeleted})
|
||||||
if err != nil {
|
|
||||||
h.Runtime.Rollback(ctx.Transaction)
|
|
||||||
response.WriteServerError(w, method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Runtime.Commit(ctx.Transaction)
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
|
|
|
@ -190,6 +190,10 @@ type DocumentStorer interface {
|
||||||
DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error)
|
DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error)
|
||||||
GetVersions(ctx domain.RequestContext, groupID string) (v []doc.Version, err error)
|
GetVersions(ctx domain.RequestContext, groupID string) (v []doc.Version, err error)
|
||||||
MoveActivity(ctx domain.RequestContext, documentID, oldSpaceID, newSpaceID string) (err error)
|
MoveActivity(ctx domain.RequestContext, documentID, oldSpaceID, newSpaceID string) (err error)
|
||||||
|
Pin(ctx domain.RequestContext, documentID string, seq int) (err error)
|
||||||
|
Unpin(ctx domain.RequestContext, documentID string) (err error)
|
||||||
|
PinSequence(ctx domain.RequestContext, spaceID string) (max int, err error)
|
||||||
|
Pinned(ctx domain.RequestContext, spaceID string) (d []doc.Document, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettingStorer defines required methods for persisting global and user level settings
|
// SettingStorer defines required methods for persisting global and user level settings
|
||||||
|
@ -228,7 +232,7 @@ type LinkStorer interface {
|
||||||
|
|
||||||
// ActivityStorer defines required methods for persisting document activity
|
// ActivityStorer defines required methods for persisting document activity
|
||||||
type ActivityStorer interface {
|
type ActivityStorer interface {
|
||||||
RecordUserActivity(ctx domain.RequestContext, activity activity.UserActivity) (err error)
|
RecordUserActivity(ctx domain.RequestContext, activity activity.UserActivity)
|
||||||
GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error)
|
GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error)
|
||||||
DeleteDocumentChangeActivity(ctx domain.RequestContext, id string) (rows int64, err error)
|
DeleteDocumentChangeActivity(ctx domain.RequestContext, id string) (rows int64, err error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,9 @@ export default Component.extend({
|
||||||
hasDocumentActions: computed('permissions.{documentDelete,documentMove}', function() {
|
hasDocumentActions: computed('permissions.{documentDelete,documentMove}', function() {
|
||||||
return this.get('permissions.documentDelete') || this.get('permissions.documentMove');
|
return this.get('permissions.documentDelete') || this.get('permissions.documentMove');
|
||||||
}),
|
}),
|
||||||
|
hasCategoryFilter: computed('categoryFilter', function() {
|
||||||
|
return !_.isEmpty(this.get('categoryFilter'));
|
||||||
|
}),
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
@ -53,7 +56,7 @@ export default Component.extend({
|
||||||
let viewDensity = this.get('localStorage').getSessionItem('space.density');
|
let viewDensity = this.get('localStorage').getSessionItem('space.density');
|
||||||
if (!_.isNull(viewDensity) && !_.isUndefined(viewDensity)) {
|
if (!_.isNull(viewDensity) && !_.isUndefined(viewDensity)) {
|
||||||
this.set('viewDensity', viewDensity);
|
this.set('viewDensity', viewDensity);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -86,7 +89,7 @@ export default Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
onSortBy(attacher) {
|
onSortBy(attacher) {
|
||||||
// attacher.hide();
|
// attacher.hide();
|
||||||
this.get('onFiltered')(this.get('documents'));
|
this.get('onFiltered')(this.get('documents'));
|
||||||
},
|
},
|
||||||
|
@ -161,6 +164,18 @@ export default Component.extend({
|
||||||
|
|
||||||
this.set('selectedCaption', list.length > 1 ? 'documents' : 'document');
|
this.set('selectedCaption', list.length > 1 ? 'documents' : 'document');
|
||||||
this.set('selectedDocuments', A(list));
|
this.set('selectedDocuments', A(list));
|
||||||
}
|
},
|
||||||
|
|
||||||
|
onPin(documentId) {
|
||||||
|
this.get('onPin')(documentId);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnpin(documentId) {
|
||||||
|
this.get('onUnpin')(documentId);
|
||||||
|
},
|
||||||
|
|
||||||
|
onPinSequence(documentId, direction) {
|
||||||
|
this.get('onPinSequence')(documentId, direction);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default Component.extend(AuthMixin, {
|
||||||
this.setup();
|
this.setup();
|
||||||
},
|
},
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didUpdateAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.setup();
|
this.setup();
|
||||||
},
|
},
|
||||||
|
@ -138,6 +138,7 @@ export default Component.extend(AuthMixin, {
|
||||||
|
|
||||||
this.set('selectedFilter', filter);
|
this.set('selectedFilter', filter);
|
||||||
this.set('categories', categories);
|
this.set('categories', categories);
|
||||||
|
|
||||||
this.get('onFiltered')(filtered);
|
this.get('onFiltered')(filtered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ import EmberObject from "@ember/object";
|
||||||
// let constants = this.get('constants');
|
// let constants = this.get('constants');
|
||||||
|
|
||||||
let constants = EmberObject.extend({
|
let constants = EmberObject.extend({
|
||||||
|
Unsequenced: 99999,
|
||||||
|
|
||||||
SpaceType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
SpaceType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
||||||
Public: 1,
|
Public: 1,
|
||||||
Private: 2,
|
Private: 2,
|
||||||
|
|
|
@ -30,6 +30,7 @@ export default Model.extend({
|
||||||
versioned: attr('boolean'),
|
versioned: attr('boolean'),
|
||||||
versionId: attr('string'),
|
versionId: attr('string'),
|
||||||
versionOrder: attr('number', { defaultValue: 0 }),
|
versionOrder: attr('number', { defaultValue: 0 }),
|
||||||
|
sequence: attr('number', { defaultValue: 99999 }),
|
||||||
groupId: attr('string'),
|
groupId: attr('string'),
|
||||||
created: attr(),
|
created: attr(),
|
||||||
revised: attr(),
|
revised: attr(),
|
||||||
|
@ -46,12 +47,12 @@ export default Model.extend({
|
||||||
|
|
||||||
isDraft: computed('lifecycle', function () {
|
isDraft: computed('lifecycle', function () {
|
||||||
let constants = this.get('constants');
|
let constants = this.get('constants');
|
||||||
return this.get('lifecycle') == constants.Lifecycle.Draft;
|
return this.get('lifecycle') === constants.Lifecycle.Draft;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
isLive: computed('lifecycle', function () {
|
isLive: computed('lifecycle', function () {
|
||||||
let constants = this.get('constants');
|
let constants = this.get('constants');
|
||||||
return this.get('lifecycle') == constants.Lifecycle.Live;
|
return this.get('lifecycle') === constants.Lifecycle.Live;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycleLabel: computed('lifecycle', function () {
|
lifecycleLabel: computed('lifecycle', function () {
|
||||||
|
@ -77,5 +78,10 @@ export default Model.extend({
|
||||||
let after = moment().subtract(7, 'days');
|
let after = moment().subtract(7, 'days');
|
||||||
return moment(this.get('revised')).isSameOrAfter(after) &&
|
return moment(this.get('revised')).isSameOrAfter(after) &&
|
||||||
moment(this.get('created')).isBefore(after);
|
moment(this.get('created')).isBefore(after);
|
||||||
})
|
}),
|
||||||
|
|
||||||
|
isSequenced: computed('sequence', function () {
|
||||||
|
let constants = this.get('constants');
|
||||||
|
return this.get('sequence') !== constants.Unsequenced;
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
@ -95,29 +95,54 @@ export default Controller.extend(NotifierMixin, {
|
||||||
onFiltered(docs) {
|
onFiltered(docs) {
|
||||||
let ls = this.get('localStorage');
|
let ls = this.get('localStorage');
|
||||||
let sortBy = this.get('sortBy');
|
let sortBy = this.get('sortBy');
|
||||||
|
let constants = this.get('constants');
|
||||||
|
|
||||||
if (_.isNull(docs)) return;
|
if (_.isNull(docs)) return;
|
||||||
|
|
||||||
|
let pinned = _.filter(docs, function(d) { return d.get('sequence') !== constants.Unsequenced; })
|
||||||
|
let unpinned = _.filter(docs, function(d) { return d.get('sequence') === constants.Unsequenced; })
|
||||||
|
|
||||||
if (sortBy.name) {
|
if (sortBy.name) {
|
||||||
docs = docs.sortBy('name');
|
unpinned = unpinned.sortBy('name');
|
||||||
ls.storeSessionItem('space.sortBy', 'name');
|
ls.storeSessionItem('space.sortBy', 'name');
|
||||||
}
|
}
|
||||||
if (sortBy.created) {
|
if (sortBy.created) {
|
||||||
docs = docs.sortBy('created');
|
unpinned = unpinned.sortBy('created');
|
||||||
ls.storeSessionItem('space.sortBy', 'created');
|
ls.storeSessionItem('space.sortBy', 'created');
|
||||||
}
|
}
|
||||||
if (sortBy.updated) {
|
if (sortBy.updated) {
|
||||||
docs = docs.sortBy('revised');
|
unpinned = unpinned.sortBy('revised');
|
||||||
ls.storeSessionItem('space.sortBy', 'updated');
|
ls.storeSessionItem('space.sortBy', 'updated');
|
||||||
}
|
}
|
||||||
if (sortBy.desc) {
|
if (sortBy.desc) {
|
||||||
docs = docs.reverseObjects();
|
unpinned = unpinned.reverseObjects();
|
||||||
ls.storeSessionItem('space.sortOrder', 'desc');
|
ls.storeSessionItem('space.sortOrder', 'desc');
|
||||||
} else {
|
} else {
|
||||||
ls.storeSessionItem('space.sortOrder', 'asc');
|
ls.storeSessionItem('space.sortOrder', 'asc');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set('filteredDocs', docs);
|
this.set('filteredDocs', _.concat(pinned, unpinned));
|
||||||
}
|
},
|
||||||
|
|
||||||
|
onPin(documentId) {
|
||||||
|
this.get('documentSvc').pin(documentId).then(() => {
|
||||||
|
this.notifySuccess('Pinned');
|
||||||
|
this.send('onRefresh');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnpin(documentId) {
|
||||||
|
this.get('documentSvc').unpin(documentId).then(() => {
|
||||||
|
this.notifySuccess('Unpinned');
|
||||||
|
this.send('onRefresh');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onPinSequence(documentId, direction) {
|
||||||
|
this.get('documentSvc').onPinSequence(documentId, direction).then(() => {
|
||||||
|
this.notifySuccess('Moved');
|
||||||
|
this.send('onRefresh');
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -71,7 +71,11 @@
|
||||||
space=model.folder
|
space=model.folder
|
||||||
templates=model.templates
|
templates=model.templates
|
||||||
permissions=model.permissions
|
permissions=model.permissions
|
||||||
|
categoryFilter=category
|
||||||
sortBy=sortBy
|
sortBy=sortBy
|
||||||
|
onPin=(action "onPin")
|
||||||
|
onUnpin=(action "onUnpin")
|
||||||
|
onPinSequence=(action "onPinSequence")
|
||||||
onFiltered=(action "onFiltered")
|
onFiltered=(action "onFiltered")
|
||||||
onExportDocument=(action "onExportDocument")
|
onExportDocument=(action "onExportDocument")
|
||||||
onDeleteDocument=(action "onDeleteDocument")
|
onDeleteDocument=(action "onDeleteDocument")
|
||||||
|
|
|
@ -567,7 +567,37 @@ export default Service.extend({
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
return error;
|
return error;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
//**************************************************
|
||||||
|
// Pinning documents inside spaces.
|
||||||
|
//**************************************************
|
||||||
|
|
||||||
|
// Pin document
|
||||||
|
pin(documentId) {
|
||||||
|
return this.get('ajax').request(`document/pin/${documentId}`, {
|
||||||
|
method: 'POST'
|
||||||
|
}).then((response) => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Unpin document
|
||||||
|
unpin(documentId) {
|
||||||
|
return this.get('ajax').request(`document/unpin/${documentId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
}).then((response) => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onPinSequence(documentId, direction) {
|
||||||
|
return this.get('ajax').request(`document/pinmove/${documentId}?direction=${direction}`, {
|
||||||
|
method: 'POST'
|
||||||
|
}).then((response) => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function isObject(a) {
|
function isObject(a) {
|
||||||
|
|
|
@ -34,6 +34,10 @@
|
||||||
> .checkbox {
|
> .checkbox {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .sequence {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .checkbox {
|
> .checkbox {
|
||||||
|
@ -51,6 +55,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .sequence {
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
top: 10px;
|
||||||
|
right: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
> .dicon {
|
||||||
|
color: map-get($yellow-shades, 700);
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .actions {
|
> .actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -68,6 +87,17 @@
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: map-get($gray-shades, 800);
|
color: map-get($gray-shades, 800);
|
||||||
|
|
||||||
|
> .pinned {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
> .dicon {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: map-get($gray-shades, 600);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .desc {
|
> .desc {
|
||||||
|
@ -168,6 +198,10 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .sequence {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
> .actions {
|
> .actions {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,13 +63,22 @@
|
||||||
{{#each documents key="id" as |document|}}
|
{{#each documents key="id" as |document|}}
|
||||||
<li class="document {{if document.selected "selected"}}" id="document-{{document.id}}">
|
<li class="document {{if document.selected "selected"}}" id="document-{{document.id}}">
|
||||||
{{#link-to "document.index" space.id space.slug document.id document.slug class="info"}}
|
{{#link-to "document.index" space.id space.slug document.id document.slug class="info"}}
|
||||||
<div class="name">{{ document.name }}</div>
|
<div class="name">
|
||||||
|
{{ document.name }}
|
||||||
|
{{#if document.isSequenced}}
|
||||||
|
<div class="pinned">
|
||||||
|
<i class="dicon {{constants.Icon.TickDouble}}">
|
||||||
|
{{#attach-tooltip showDelay=250}}Pinned{{/attach-tooltip}}
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
{{#if (not-eq viewDensity "3")}}
|
{{#if (not-eq viewDensity "3")}}
|
||||||
<div class="desc">{{ document.excerpt }}</div>
|
<div class="desc">{{ document.excerpt }}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (eq viewDensity "1")}}
|
{{#if (eq viewDensity "1")}}
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<div class="lifecycle">
|
<div class="lifecycle">
|
||||||
<div class="{{if (eq document.lifecycle constants.Lifecycle.Draft) "draft"}}
|
<div class="{{if (eq document.lifecycle constants.Lifecycle.Draft) "draft"}}
|
||||||
{{if (eq document.lifecycle constants.Lifecycle.Live) "live"}}
|
{{if (eq document.lifecycle constants.Lifecycle.Live) "live"}}
|
||||||
{{if (eq document.lifecycle constants.Lifecycle.Archived) "archived"}}">
|
{{if (eq document.lifecycle constants.Lifecycle.Archived) "archived"}}">
|
||||||
|
@ -83,6 +92,25 @@
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
|
|
||||||
{{#if hasDocumentActions}}
|
{{#if hasDocumentActions}}
|
||||||
|
<div class="sequence">
|
||||||
|
{{#if document.isSequenced}}
|
||||||
|
{{#unless hasCategoryFilter}}
|
||||||
|
<i class="dicon {{constants.Icon.ArrowSmallUp}}" {{action "onPinSequence" document.id "u"}}>
|
||||||
|
{{#attach-tooltip showDelay=250}}Move up{{/attach-tooltip}}
|
||||||
|
</i>
|
||||||
|
<i class="dicon {{constants.Icon.ArrowSmallDown}}" {{action "onPinSequence" document.id "d"}}>
|
||||||
|
{{#attach-tooltip showDelay=250}}Move down{{/attach-tooltip}}
|
||||||
|
</i>
|
||||||
|
{{/unless}}
|
||||||
|
<i class="dicon {{constants.Icon.Cross}}" {{action "onUnpin" document.id}}>
|
||||||
|
{{#attach-tooltip showDelay=250}}Unpin{{/attach-tooltip}}
|
||||||
|
</i>
|
||||||
|
{{else}}
|
||||||
|
<i class="dicon {{constants.Icon.ArrowSmallUp}}" {{action "onPin" document.id}}>
|
||||||
|
{{#attach-tooltip showDelay=250}}Pin{{/attach-tooltip}}
|
||||||
|
</i>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
<div class="checkbox" {{action "selectDocument" document.id}}>
|
<div class="checkbox" {{action "selectDocument" document.id}}>
|
||||||
{{#if document.selected}}
|
{{#if document.selected}}
|
||||||
<i class="dicon {{constants.Icon.CheckboxChecked}}"/>
|
<i class="dicon {{constants.Icon.CheckboxChecked}}"/>
|
||||||
|
|
|
@ -115,6 +115,15 @@ const (
|
||||||
|
|
||||||
// TypePublished happens when a document is moved from Draft to Live.
|
// TypePublished happens when a document is moved from Draft to Live.
|
||||||
TypePublished Type = 16
|
TypePublished Type = 16
|
||||||
|
|
||||||
|
// TypePinned happens when a document is pinned within space.
|
||||||
|
TypePinned Type = 17
|
||||||
|
|
||||||
|
// TypeUnpinned happens when a document is no longer pinned inside a space.
|
||||||
|
TypeUnpinned Type = 18
|
||||||
|
|
||||||
|
// TypePinSequence is when the order of sequenced documents is changed.
|
||||||
|
TypePinSequence Type = 19
|
||||||
)
|
)
|
||||||
|
|
||||||
// TypeName returns one-work descriptor for activity type
|
// TypeName returns one-work descriptor for activity type
|
||||||
|
@ -152,6 +161,12 @@ func TypeName(t Type) string {
|
||||||
return "Search"
|
return "Search"
|
||||||
case TypePublished:
|
case TypePublished:
|
||||||
return "Publish"
|
return "Publish"
|
||||||
|
case TypePinned:
|
||||||
|
return "Pinned"
|
||||||
|
case TypeUnpinned:
|
||||||
|
return "Unpinned"
|
||||||
|
case TypePinSequence:
|
||||||
|
return "Sequence"
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -38,6 +38,7 @@ type Document struct {
|
||||||
Versioned bool `json:"versioned"`
|
Versioned bool `json:"versioned"`
|
||||||
VersionID string `json:"versionId"`
|
VersionID string `json:"versionId"`
|
||||||
VersionOrder int `json:"versionOrder"`
|
VersionOrder int `json:"versionOrder"`
|
||||||
|
Sequence int `json:"sequence"`
|
||||||
GroupID string `json:"groupId"`
|
GroupID string `json:"groupId"`
|
||||||
|
|
||||||
// Read-only presentation only data
|
// Read-only presentation only data
|
||||||
|
@ -67,6 +68,13 @@ func (a ByID) Len() int { return len(a) }
|
||||||
func (a ByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a ByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a ByID) Less(i, j int) bool { return a[i].RefID > a[j].RefID }
|
func (a ByID) Less(i, j int) bool { return a[i].RefID > a[j].RefID }
|
||||||
|
|
||||||
|
// BySeq sorts a collection of documents by sequenced number.
|
||||||
|
type BySeq []Document
|
||||||
|
|
||||||
|
func (a BySeq) Len() int { return len(a) }
|
||||||
|
func (a BySeq) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a BySeq) Less(i, j int) bool { return a[i].Sequence < a[j].Sequence }
|
||||||
|
|
||||||
// DocumentMeta details who viewed the document.
|
// DocumentMeta details who viewed the document.
|
||||||
type DocumentMeta struct {
|
type DocumentMeta struct {
|
||||||
Viewers []DocumentMetaViewer `json:"viewers"`
|
Viewers []DocumentMetaViewer `json:"viewers"`
|
||||||
|
@ -118,3 +126,15 @@ type DuplicateModel struct {
|
||||||
DocumentID string `json:"documentId"`
|
DocumentID string `json:"documentId"`
|
||||||
Name string `json:"documentName"`
|
Name string `json:"documentName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SortedDocs provides list od pinned and unpinned documents
|
||||||
|
// sorted by sequence and name respectively.
|
||||||
|
type SortedDocs struct {
|
||||||
|
Pinned []Document `json:"pinned"`
|
||||||
|
Unpinned []Document `json:"unpinned"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Unsequenced tells us if document is pinned or not
|
||||||
|
Unsequenced int = 99999
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue