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.
|
||||
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.UserID = ctx.UserID
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute record user activity")
|
||||
s.Runtime.Log.Error("execute record user activity", err)
|
||||
}
|
||||
|
||||
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_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_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)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "select.document")
|
||||
|
|
|
@ -14,6 +14,7 @@ package document
|
|||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
@ -88,18 +89,12 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: document.SpaceID,
|
||||
DocumentID: document.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
|
||||
|
@ -190,10 +185,26 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// Sort document list by title.
|
||||
sort.Sort(doc.ByName(filtered))
|
||||
sortedDocs := doc.SortedDocs{}
|
||||
|
||||
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
|
||||
|
@ -249,7 +260,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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.Store.Space.SetStats(ctx, d.SpaceID)
|
||||
_ = h.Store.Space.SetStats(ctx, 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)
|
||||
|
@ -480,18 +491,13 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: "",
|
||||
DocumentID: "",
|
||||
Metadata: options.Keywords,
|
||||
SourceType: activity.SourceTypeSearch,
|
||||
ActivityType: activity.TypeSearched})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
}
|
||||
|
@ -526,18 +532,13 @@ func (h *Handler) recordSearchActivity(ctx domain.RequestContext, q []search.Que
|
|||
}
|
||||
|
||||
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,
|
||||
DocumentID: q[i].DocumentID,
|
||||
Metadata: keywords,
|
||||
SourceType: activity.SourceTypeSearch,
|
||||
ActivityType: activity.TypeSearched})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
prev[q[i].DocumentID] = true
|
||||
}
|
||||
}
|
||||
|
@ -713,16 +714,11 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if document.Lifecycle == workflow.LifecycleLive {
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: document.SpaceID,
|
||||
DocumentID: document.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
}
|
||||
|
||||
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.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(export))
|
||||
_, _ = w.Write([]byte(export))
|
||||
}
|
||||
|
||||
// Duplicate makes a copy of a document.
|
||||
|
@ -1014,3 +1010,219 @@ func (h *Handler) Duplicate(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/store"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// 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(`
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_seq, c_groupid,
|
||||
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.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 {
|
||||
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_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_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
|
||||
WHERE c_orgid=? AND c_refid=?`),
|
||||
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_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_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
|
||||
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
|
||||
|
@ -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_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_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
|
||||
WHERE c_orgid=? AND c_spaceid=? AND c_template=`+s.IsTrue()+` AND c_lifecycle=1
|
||||
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_desc=:excerpt, c_slug=:slug, c_tags=:tags, c_template=:template,
|
||||
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
|
||||
WHERE c_orgid=:orgid AND c_refid=:refid`),
|
||||
&document)
|
||||
|
@ -331,3 +335,78 @@ func (s Store) GetVersions(ctx domain.RequestContext, groupID string) (v []doc.V
|
|||
|
||||
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 {
|
||||
h.Runtime.Log.Error(method, err)
|
||||
} else {
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: doc.SpaceID,
|
||||
DocumentID: doc.RefID,
|
||||
Metadata: source, // deliberate
|
||||
SourceType: activity.SourceTypeSearch, // deliberate
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,14 +138,10 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: sp.RefID,
|
||||
SourceType: activity.SourceTypeSpace,
|
||||
ActivityType: activity.TypeCreated})
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
|
@ -444,16 +440,11 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: sp.RefID,
|
||||
SourceType: activity.SourceTypeSpace,
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteJSON(w, sp)
|
||||
|
@ -657,14 +648,10 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: id,
|
||||
SourceType: activity.SourceTypeSpace,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
|
@ -764,14 +751,10 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: id,
|
||||
SourceType: activity.SourceTypeSpace,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(ctx.Transaction)
|
||||
response.WriteServerError(w, method, err)
|
||||
}
|
||||
|
||||
h.Runtime.Commit(ctx.Transaction)
|
||||
|
||||
|
|
|
@ -190,6 +190,10 @@ type DocumentStorer interface {
|
|||
DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error)
|
||||
GetVersions(ctx domain.RequestContext, groupID string) (v []doc.Version, 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
|
||||
|
@ -228,7 +232,7 @@ type LinkStorer interface {
|
|||
|
||||
// ActivityStorer defines required methods for persisting document activity
|
||||
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)
|
||||
DeleteDocumentChangeActivity(ctx domain.RequestContext, id string) (rows int64, err error)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ export default Component.extend({
|
|||
hasDocumentActions: computed('permissions.{documentDelete,documentMove}', function() {
|
||||
return this.get('permissions.documentDelete') || this.get('permissions.documentMove');
|
||||
}),
|
||||
hasCategoryFilter: computed('categoryFilter', function() {
|
||||
return !_.isEmpty(this.get('categoryFilter'));
|
||||
}),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
@ -53,7 +56,7 @@ export default Component.extend({
|
|||
let viewDensity = this.get('localStorage').getSessionItem('space.density');
|
||||
if (!_.isNull(viewDensity) && !_.isUndefined(viewDensity)) {
|
||||
this.set('viewDensity', viewDensity);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
@ -86,7 +89,7 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onSortBy(attacher) {
|
||||
onSortBy(attacher) {
|
||||
// attacher.hide();
|
||||
this.get('onFiltered')(this.get('documents'));
|
||||
},
|
||||
|
@ -161,6 +164,18 @@ export default Component.extend({
|
|||
|
||||
this.set('selectedCaption', list.length > 1 ? 'documents' : 'document');
|
||||
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();
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
didUpdateAttrs() {
|
||||
this._super(...arguments);
|
||||
this.setup();
|
||||
},
|
||||
|
@ -138,6 +138,7 @@ export default Component.extend(AuthMixin, {
|
|||
|
||||
this.set('selectedFilter', filter);
|
||||
this.set('categories', categories);
|
||||
|
||||
this.get('onFiltered')(filtered);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import EmberObject from "@ember/object";
|
|||
// let constants = this.get('constants');
|
||||
|
||||
let constants = EmberObject.extend({
|
||||
Unsequenced: 99999,
|
||||
|
||||
SpaceType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
||||
Public: 1,
|
||||
Private: 2,
|
||||
|
|
|
@ -30,6 +30,7 @@ export default Model.extend({
|
|||
versioned: attr('boolean'),
|
||||
versionId: attr('string'),
|
||||
versionOrder: attr('number', { defaultValue: 0 }),
|
||||
sequence: attr('number', { defaultValue: 99999 }),
|
||||
groupId: attr('string'),
|
||||
created: attr(),
|
||||
revised: attr(),
|
||||
|
@ -46,12 +47,12 @@ export default Model.extend({
|
|||
|
||||
isDraft: computed('lifecycle', function () {
|
||||
let constants = this.get('constants');
|
||||
return this.get('lifecycle') == constants.Lifecycle.Draft;
|
||||
return this.get('lifecycle') === constants.Lifecycle.Draft;
|
||||
}),
|
||||
|
||||
isLive: computed('lifecycle', function () {
|
||||
let constants = this.get('constants');
|
||||
return this.get('lifecycle') == constants.Lifecycle.Live;
|
||||
return this.get('lifecycle') === constants.Lifecycle.Live;
|
||||
}),
|
||||
|
||||
lifecycleLabel: computed('lifecycle', function () {
|
||||
|
@ -77,5 +78,10 @@ export default Model.extend({
|
|||
let after = moment().subtract(7, 'days');
|
||||
return moment(this.get('revised')).isSameOrAfter(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) {
|
||||
let ls = this.get('localStorage');
|
||||
let sortBy = this.get('sortBy');
|
||||
let constants = this.get('constants');
|
||||
|
||||
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) {
|
||||
docs = docs.sortBy('name');
|
||||
unpinned = unpinned.sortBy('name');
|
||||
ls.storeSessionItem('space.sortBy', 'name');
|
||||
}
|
||||
if (sortBy.created) {
|
||||
docs = docs.sortBy('created');
|
||||
unpinned = unpinned.sortBy('created');
|
||||
ls.storeSessionItem('space.sortBy', 'created');
|
||||
}
|
||||
if (sortBy.updated) {
|
||||
docs = docs.sortBy('revised');
|
||||
unpinned = unpinned.sortBy('revised');
|
||||
ls.storeSessionItem('space.sortBy', 'updated');
|
||||
}
|
||||
if (sortBy.desc) {
|
||||
docs = docs.reverseObjects();
|
||||
unpinned = unpinned.reverseObjects();
|
||||
ls.storeSessionItem('space.sortOrder', 'desc');
|
||||
} else {
|
||||
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
|
||||
templates=model.templates
|
||||
permissions=model.permissions
|
||||
categoryFilter=category
|
||||
sortBy=sortBy
|
||||
onPin=(action "onPin")
|
||||
onUnpin=(action "onUnpin")
|
||||
onPinSequence=(action "onPinSequence")
|
||||
onFiltered=(action "onFiltered")
|
||||
onExportDocument=(action "onExportDocument")
|
||||
onDeleteDocument=(action "onDeleteDocument")
|
||||
|
|
|
@ -567,7 +567,37 @@ export default Service.extend({
|
|||
}).catch((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) {
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
> .checkbox {
|
||||
display: block;
|
||||
}
|
||||
|
||||
> .sequence {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
> .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 {
|
||||
position: absolute;
|
||||
display: none;
|
||||
|
@ -68,6 +87,17 @@
|
|||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
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 {
|
||||
|
@ -168,6 +198,10 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
> .sequence {
|
||||
display: block;
|
||||
}
|
||||
|
||||
> .actions {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -63,13 +63,22 @@
|
|||
{{#each documents key="id" as |document|}}
|
||||
<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"}}
|
||||
<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")}}
|
||||
<div class="desc">{{ document.excerpt }}</div>
|
||||
{{/if}}
|
||||
{{#if (eq viewDensity "1")}}
|
||||
<div class="meta">
|
||||
<div class="lifecycle">
|
||||
<div class="lifecycle">
|
||||
<div class="{{if (eq document.lifecycle constants.Lifecycle.Draft) "draft"}}
|
||||
{{if (eq document.lifecycle constants.Lifecycle.Live) "live"}}
|
||||
{{if (eq document.lifecycle constants.Lifecycle.Archived) "archived"}}">
|
||||
|
@ -83,6 +92,25 @@
|
|||
{{/link-to}}
|
||||
|
||||
{{#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}}>
|
||||
{{#if document.selected}}
|
||||
<i class="dicon {{constants.Icon.CheckboxChecked}}"/>
|
||||
|
|
|
@ -115,6 +115,15 @@ const (
|
|||
|
||||
// TypePublished happens when a document is moved from Draft to Live.
|
||||
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
|
||||
|
@ -152,6 +161,12 @@ func TypeName(t Type) string {
|
|||
return "Search"
|
||||
case TypePublished:
|
||||
return "Publish"
|
||||
case TypePinned:
|
||||
return "Pinned"
|
||||
case TypeUnpinned:
|
||||
return "Unpinned"
|
||||
case TypePinSequence:
|
||||
return "Sequence"
|
||||
}
|
||||
|
||||
return ""
|
||||
|
|
|
@ -38,6 +38,7 @@ type Document struct {
|
|||
Versioned bool `json:"versioned"`
|
||||
VersionID string `json:"versionId"`
|
||||
VersionOrder int `json:"versionOrder"`
|
||||
Sequence int `json:"sequence"`
|
||||
GroupID string `json:"groupId"`
|
||||
|
||||
// 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) 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.
|
||||
type DocumentMeta struct {
|
||||
Viewers []DocumentMetaViewer `json:"viewers"`
|
||||
|
@ -118,3 +126,15 @@ type DuplicateModel struct {
|
|||
DocumentID string `json:"documentId"`
|
||||
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