mirror of
https://github.com/documize/community.git
synced 2025-07-23 23:29:42 +02:00
commit
6738d2c9e1
52 changed files with 1521 additions and 982 deletions
16
README.md
16
README.md
|
@ -1,9 +1,3 @@
|
||||||
> *We provide frequent product updates for both cloud and self-hosted customers.*
|
|
||||||
>
|
|
||||||
> **Harvey Kandola / CEO & Founder / Documize, Inc.**
|
|
||||||
|
|
||||||
## Welcome
|
|
||||||
|
|
||||||
Documize is an open source modern, lightweight and comprehensive alternative to Confluence.
|
Documize is an open source modern, lightweight and comprehensive alternative to Confluence.
|
||||||
|
|
||||||
It's built with Golang + EmberJS and compiled down to a single executable binary for Linux, Windows and macOS.
|
It's built with Golang + EmberJS and compiled down to a single executable binary for Linux, Windows and macOS.
|
||||||
|
@ -19,9 +13,13 @@ All you need to provide is PostgreSQL or any MySQL variant.
|
||||||
|
|
||||||
## Latest Release
|
## Latest Release
|
||||||
|
|
||||||
[Community Edition: v2.1.1](https://github.com/documize/community/releases)
|
[Community Edition: v2.2.0](https://github.com/documize/community/releases)
|
||||||
|
|
||||||
[Enterprise Edition: v2.1.1](https://www.documize.com/downloads)
|
[Enterprise Edition: v2.2.0](https://www.documize.com/downloads)
|
||||||
|
|
||||||
|
> *We provide frequent product updates for both cloud and self-hosted customers.*
|
||||||
|
>
|
||||||
|
> **Harvey Kandola, CEO/Founder, Documize, Inc.**
|
||||||
|
|
||||||
## OS support
|
## OS support
|
||||||
|
|
||||||
|
@ -50,7 +48,7 @@ Heck, Documize will probably run just fine on a Raspberry Pi 3.
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
- Go (v1.12.0)
|
- Go (v1.12.1)
|
||||||
- Ember JS (v3.8.0)
|
- Ember JS (v3.8.0)
|
||||||
|
|
||||||
## Authentication Options
|
## Authentication Options
|
||||||
|
|
59
core/env/runtime.go
vendored
59
core/env/runtime.go
vendored
|
@ -13,25 +13,25 @@
|
||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SiteModeNormal serves app
|
// SiteModeNormal serves app
|
||||||
SiteModeNormal = ""
|
SiteModeNormal = ""
|
||||||
|
|
||||||
// SiteModeOffline serves offline.html
|
// SiteModeOffline serves offline.html
|
||||||
SiteModeOffline = "1"
|
SiteModeOffline = "1"
|
||||||
|
|
||||||
// SiteModeSetup tells Ember to serve setup route
|
// SiteModeSetup tells Ember to serve setup route
|
||||||
SiteModeSetup = "2"
|
SiteModeSetup = "2"
|
||||||
|
|
||||||
// SiteModeBadDB redirects to db-error.html page
|
// SiteModeBadDB redirects to db-error.html page
|
||||||
SiteModeBadDB = "3"
|
SiteModeBadDB = "3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runtime provides access to database, logger and other server-level scoped objects.
|
// Runtime provides access to database, logger and other server-level scoped objects.
|
||||||
|
@ -44,40 +44,37 @@ type Runtime struct {
|
||||||
Product domain.Product
|
Product domain.Product
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctx = context.Background()
|
|
||||||
|
|
||||||
// StartTx beings database transaction with application defined
|
// StartTx beings database transaction with application defined
|
||||||
// database transaction isolation level.
|
// database transaction isolation level.
|
||||||
// Any error encountered during this operation is logged to runtime logger.
|
// Any error encountered during this operation is logged to runtime logger.
|
||||||
func (r *Runtime) StartTx() (tx *sqlx.Tx, ok bool) {
|
func (r *Runtime) StartTx(i sql.IsolationLevel) (tx *sqlx.Tx, ok bool) {
|
||||||
tx, err := r.Db.BeginTxx(ctx, &sql.TxOptions{Isolation: sql.LevelReadUncommitted})
|
tx, err := r.Db.BeginTxx(context.Background(), &sql.TxOptions{Isolation: i})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Log.Error("unable to start database transaction", err)
|
r.Log.Error("unable to start database transaction", err)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx, true
|
return tx, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rollback aborts active database transaction.
|
// Rollback aborts active database transaction.
|
||||||
// Any error encountered during this operation is logged to runtime logger.
|
// Any error encountered during this operation is logged to runtime logger.
|
||||||
func (r *Runtime) Rollback(tx *sqlx.Tx) bool {
|
func (r *Runtime) Rollback(tx *sqlx.Tx) bool {
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
r.Log.Error("unable to commit database transaction", err)
|
r.Log.Error("unable to commit database transaction", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit flushes pending changes to database.
|
// Commit flushes pending changes to database.
|
||||||
// Any error encountered during this operation is logged to runtime logger.
|
// Any error encountered during this operation is logged to runtime logger.
|
||||||
func (r *Runtime) Commit(tx *sqlx.Tx) bool {
|
func (r *Runtime) Commit(tx *sqlx.Tx) bool {
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
r.Log.Error("unable to commit database transaction", err)
|
r.Log.Error("unable to commit database transaction", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get attachment being requested.
|
// Get attachment being requested.
|
||||||
a, err := h.Store.Attachment.GetAttachment(ctx, ctx.OrgID, request.Param(r, "attachmentID"))
|
a, err := h.Store.Attachment.GetAttachment(ctx, ctx.OrgID, request.Param(r, "attachmentID"))
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
response.WriteNotFoundError(w, method, request.Param(r, "fileID"))
|
response.WriteNotFoundError(w, method, request.Param(r, "attachmentID"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -161,6 +161,12 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
||||||
canDownload = true
|
canDownload = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(secureToken) == 0 && len(authToken) == 0 {
|
||||||
|
h.Runtime.Log.Error("get attachment received no access token", err)
|
||||||
|
response.WriteForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Send back error if caller unable view attachment
|
// Send back error if caller unable view attachment
|
||||||
if !canDownload {
|
if !canDownload {
|
||||||
h.Runtime.Log.Error("get attachment refused", err)
|
h.Runtime.Log.Error("get attachment refused", err)
|
||||||
|
|
|
@ -273,6 +273,7 @@ func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
|
||||||
// Check for required fields.
|
// Check for required fields.
|
||||||
if len(username) == 0 || len(password) == 0 {
|
if len(username) == 0 || len(password) == 0 {
|
||||||
response.WriteUnauthorizedError(w)
|
response.WriteUnauthorizedError(w)
|
||||||
|
h.Runtime.Log.Info("LDAP authentication aborted due to missing username/password")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,6 +314,7 @@ func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("LDAP failed login request for " + username + " @ " + dom)
|
||||||
response.WriteUnauthorizedError(w)
|
response.WriteUnauthorizedError(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,6 +179,18 @@ func (s Store) Update(ctx domain.RequestContext, document doc.Document) (err err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateRevised sets document revision date to UTC now.
|
||||||
|
func (s Store) UpdateRevised(ctx domain.RequestContext, docID string) (err error) {
|
||||||
|
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_doc SET c_revised=? WHERE c_orgid=? AND c_refid=?`),
|
||||||
|
time.Now().UTC(), ctx.OrgID, docID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "document.store.UpdateRevised")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateGroup applies same values to all documents with the same group ID.
|
// UpdateGroup applies same values to all documents with the same group ID.
|
||||||
func (s Store) UpdateGroup(ctx domain.RequestContext, d doc.Document) (err error) {
|
func (s Store) UpdateGroup(ctx domain.RequestContext, d doc.Document) (err error) {
|
||||||
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_doc SET c_name=?, c_desc=? WHERE c_orgid=? AND c_groupid=?`),
|
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_doc SET c_name=?, c_desc=? WHERE c_orgid=? AND c_groupid=?`),
|
||||||
|
|
|
@ -175,6 +175,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
ActivityType: activity.TypeCreated})
|
ActivityType: activity.TypeCreated})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update doc revised.
|
||||||
|
h.Store.Document.UpdateRevised(ctx, doc.RefID)
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionAdd)
|
h.Store.Audit.Record(ctx, audit.EventTypeSectionAdd)
|
||||||
|
@ -492,6 +495,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update doc revised.
|
||||||
|
h.Store.Document.UpdateRevised(ctx, model.Page.DocumentID)
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
if doc.Lifecycle == workflow.LifecycleLive {
|
if doc.Lifecycle == workflow.LifecycleLive {
|
||||||
|
@ -593,6 +599,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
h.Store.Page.DeletePageRevisions(ctx, pageID)
|
h.Store.Page.DeletePageRevisions(ctx, pageID)
|
||||||
|
|
||||||
|
// Update doc revised.
|
||||||
|
h.Store.Document.UpdateRevised(ctx, doc.RefID)
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
|
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
|
||||||
|
@ -702,6 +711,9 @@ func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update doc revised.
|
||||||
|
h.Store.Document.UpdateRevised(ctx, doc.RefID)
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
|
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
|
||||||
|
@ -779,6 +791,9 @@ func (h *Handler) ChangePageSequence(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update doc revised.
|
||||||
|
h.Store.Document.UpdateRevised(ctx, doc.RefID)
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionResequence)
|
h.Store.Audit.Record(ctx, audit.EventTypeSectionResequence)
|
||||||
|
@ -848,6 +863,9 @@ func (h *Handler) ChangePageLevel(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update doc revised.
|
||||||
|
h.Store.Document.UpdateRevised(ctx, doc.RefID)
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionResequence)
|
h.Store.Audit.Record(ctx, audit.EventTypeSectionResequence)
|
||||||
|
@ -969,6 +987,9 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
|
||||||
ActivityType: activity.TypeCreated})
|
ActivityType: activity.TypeCreated})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update doc revised.
|
||||||
|
h.Store.Document.UpdateRevised(ctx, targetID)
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionCopy)
|
h.Store.Audit.Record(ctx, audit.EventTypeSectionCopy)
|
||||||
|
@ -1223,6 +1244,9 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
|
||||||
ActivityType: activity.TypeReverted})
|
ActivityType: activity.TypeReverted})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update doc revised.
|
||||||
|
h.Store.Document.UpdateRevised(ctx, doc.RefID)
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSectionRollback)
|
h.Store.Audit.Record(ctx, audit.EventTypeSectionRollback)
|
||||||
|
|
|
@ -12,12 +12,14 @@
|
||||||
package search
|
package search
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
"github.com/documize/community/model/attachment"
|
"github.com/documize/community/model/attachment"
|
||||||
"github.com/documize/community/model/category"
|
"github.com/documize/community/model/category"
|
||||||
"github.com/documize/community/model/doc"
|
"github.com/documize/community/model/doc"
|
||||||
"github.com/documize/community/model/page"
|
"github.com/documize/community/model/page"
|
||||||
sm "github.com/documize/community/model/search"
|
sm "github.com/documize/community/model/search"
|
||||||
|
"github.com/documize/community/model/workflow"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IndexDocument adds search indesd entries for document inserting title, tags and attachments as
|
// IndexDocument adds search indesd entries for document inserting title, tags and attachments as
|
||||||
|
@ -26,20 +28,21 @@ func (m *Indexer) IndexDocument(ctx domain.RequestContext, d doc.Document, a []a
|
||||||
method := "search.IndexDocument"
|
method := "search.IndexDocument"
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ctx.Transaction, err = m.runtime.Db.Beginx()
|
ok := true
|
||||||
if err != nil {
|
ctx.Transaction, ok = m.runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
m.runtime.Log.Error(method, err)
|
if !ok {
|
||||||
|
m.runtime.Log.Info("unable to start TX for " + method)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.store.Search.IndexDocument(ctx, d, a)
|
err = m.store.Search.IndexDocument(ctx, d, a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
m.runtime.Rollback(ctx.Transaction)
|
||||||
m.runtime.Log.Error(method, err)
|
m.runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
m.runtime.Commit(ctx.Transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDocument removes all search entries for document.
|
// DeleteDocument removes all search entries for document.
|
||||||
|
@ -47,20 +50,21 @@ func (m *Indexer) DeleteDocument(ctx domain.RequestContext, ID string) {
|
||||||
method := "search.DeleteDocument"
|
method := "search.DeleteDocument"
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ctx.Transaction, err = m.runtime.Db.Beginx()
|
ok := true
|
||||||
if err != nil {
|
ctx.Transaction, ok = m.runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
m.runtime.Log.Error(method, err)
|
if !ok {
|
||||||
|
m.runtime.Log.Info("unable to start TX for " + method)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.store.Search.DeleteDocument(ctx, ID)
|
err = m.store.Search.DeleteDocument(ctx, ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
m.runtime.Rollback(ctx.Transaction)
|
||||||
m.runtime.Log.Error(method, err)
|
m.runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
m.runtime.Commit(ctx.Transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexContent adds search index entry for document context.
|
// IndexContent adds search index entry for document context.
|
||||||
|
@ -69,25 +73,26 @@ func (m *Indexer) IndexContent(ctx domain.RequestContext, p page.Page) {
|
||||||
method := "search.IndexContent"
|
method := "search.IndexContent"
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ctx.Transaction, err = m.runtime.Db.Beginx()
|
// we do not index pending pages
|
||||||
if err != nil {
|
if p.Status == workflow.ChangePending || p.Status == workflow.ChangePendingNew {
|
||||||
m.runtime.Log.Error(method, err)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
ctx.Transaction, ok = m.runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
m.runtime.Log.Info("unable to start TX for " + method)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.store.Search.IndexContent(ctx, p)
|
err = m.store.Search.IndexContent(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
m.runtime.Rollback(ctx.Transaction)
|
||||||
m.runtime.Log.Error(method, err)
|
m.runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Transaction.Commit()
|
m.runtime.Commit(ctx.Transaction)
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
m.runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteContent removes all search entries for specific document content.
|
// DeleteContent removes all search entries for specific document content.
|
||||||
|
@ -95,20 +100,21 @@ func (m *Indexer) DeleteContent(ctx domain.RequestContext, pageID string) {
|
||||||
method := "search.DeleteContent"
|
method := "search.DeleteContent"
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ctx.Transaction, err = m.runtime.Db.Beginx()
|
ok := true
|
||||||
if err != nil {
|
ctx.Transaction, ok = m.runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
m.runtime.Log.Error(method, err)
|
if !ok {
|
||||||
|
m.runtime.Log.Info("unable to start TX for " + method)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.store.Search.DeleteContent(ctx, pageID)
|
err = m.store.Search.DeleteContent(ctx, pageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
m.runtime.Rollback(ctx.Transaction)
|
||||||
m.runtime.Log.Error(method, err)
|
m.runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
m.runtime.Commit(ctx.Transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterCategoryProtected removes search results that cannot be seen by user
|
// FilterCategoryProtected removes search results that cannot be seen by user
|
||||||
|
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"github.com/documize/community/model/doc"
|
"github.com/documize/community/model/doc"
|
||||||
"github.com/documize/community/model/page"
|
"github.com/documize/community/model/page"
|
||||||
"github.com/documize/community/model/search"
|
"github.com/documize/community/model/search"
|
||||||
"github.com/documize/community/model/workflow"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -125,11 +124,6 @@ func (s Store) DeleteDocument(ctx domain.RequestContext, ID string) (err error)
|
||||||
func (s Store) IndexContent(ctx domain.RequestContext, p page.Page) (err error) {
|
func (s Store) IndexContent(ctx domain.RequestContext, p page.Page) (err error) {
|
||||||
method := "search.IndexContent"
|
method := "search.IndexContent"
|
||||||
|
|
||||||
// we do not index pending pages
|
|
||||||
if p.Status == workflow.ChangePending || p.Status == workflow.ChangePendingNew {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove previous search entries
|
// remove previous search entries
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=? AND c_itemid=? AND c_itemtype='page'"),
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=? AND c_itemid=? AND c_itemtype='page'"),
|
||||||
ctx.OrgID, p.DocumentID, p.RefID)
|
ctx.OrgID, p.DocumentID, p.RefID)
|
||||||
|
@ -289,7 +283,7 @@ func (s Store) matchFullText(ctx domain.RequestContext, keywords, itemType strin
|
||||||
s.id, s.c_orgid AS orgid, s.c_docid AS documentid, s.c_itemid AS itemid, s.c_itemtype AS itemtype,
|
s.id, s.c_orgid AS orgid, s.c_docid AS documentid, s.c_itemid AS itemid, s.c_itemtype AS itemtype,
|
||||||
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags,
|
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags,
|
||||||
d.c_desc AS excerpt, d.c_template AS template, d.c_versionid AS versionid,
|
d.c_desc AS excerpt, d.c_template AS template, d.c_versionid AS versionid,
|
||||||
COALESCE(l.c_name,'Unknown') AS space
|
COALESCE(l.c_name,'Unknown') AS space, d.c_created AS created, d.c_revised AS revised
|
||||||
FROM
|
FROM
|
||||||
dmz_search s,
|
dmz_search s,
|
||||||
dmz_doc d
|
dmz_doc d
|
||||||
|
@ -343,7 +337,7 @@ func (s Store) matchLike(ctx domain.RequestContext, keywords, itemType string) (
|
||||||
sql1 := s.Bind(`SELECT
|
sql1 := s.Bind(`SELECT
|
||||||
s.id, s.c_orgid AS orgid, s.c_docid AS documentid, s.c_itemid AS itemid, s.c_itemtype AS itemtype,
|
s.id, s.c_orgid AS orgid, s.c_docid AS documentid, s.c_itemid AS itemid, s.c_itemtype AS itemtype,
|
||||||
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags, d.c_desc AS excerpt,
|
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags, d.c_desc AS excerpt,
|
||||||
COALESCE(l.c_name,'Unknown') AS space
|
COALESCE(l.c_name,'Unknown') AS space, d.c_created AS created, d.c_revised AS revised
|
||||||
FROM
|
FROM
|
||||||
dmz_search s,
|
dmz_search s,
|
||||||
dmz_doc d
|
dmz_doc d
|
||||||
|
|
|
@ -36,7 +36,6 @@ func Register(rt *env.Runtime, s *store.Store) {
|
||||||
provider.Register("code", &code.Provider{Runtime: rt, Store: s})
|
provider.Register("code", &code.Provider{Runtime: rt, Store: s})
|
||||||
provider.Register("jira", &jira.Provider{Runtime: rt, Store: s})
|
provider.Register("jira", &jira.Provider{Runtime: rt, Store: s})
|
||||||
provider.Register("gemini", &gemini.Provider{Runtime: rt, Store: s})
|
provider.Register("gemini", &gemini.Provider{Runtime: rt, Store: s})
|
||||||
// provider.Register("github", &github.Provider{Runtime: rt, Store: s})
|
|
||||||
provider.Register("markdown", &markdown.Provider{Runtime: rt, Store: s})
|
provider.Register("markdown", &markdown.Provider{Runtime: rt, Store: s})
|
||||||
provider.Register("papertrail", &papertrail.Provider{Runtime: rt, Store: s})
|
provider.Register("papertrail", &papertrail.Provider{Runtime: rt, Store: s})
|
||||||
provider.Register("tabular", &tabular.Provider{Runtime: rt, Store: s})
|
provider.Register("tabular", &tabular.Provider{Runtime: rt, Store: s})
|
||||||
|
|
|
@ -696,7 +696,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Delete the space first.
|
// Delete the space first.
|
||||||
ok := true
|
ok := true
|
||||||
ctx.Transaction, ok = h.Runtime.StartTx()
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
if !ok {
|
if !ok {
|
||||||
response.WriteError(w, method)
|
response.WriteError(w, method)
|
||||||
return
|
return
|
||||||
|
@ -712,7 +712,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
h.Runtime.Commit(ctx.Transaction)
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
// Delete data associated with this space.
|
// Delete data associated with this space.
|
||||||
ctx.Transaction, ok = h.Runtime.StartTx()
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
if !ok {
|
if !ok {
|
||||||
response.WriteError(w, method)
|
response.WriteError(w, method)
|
||||||
return
|
return
|
||||||
|
@ -756,7 +756,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
h.Runtime.Commit(ctx.Transaction)
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
// Record this action.
|
// Record this action.
|
||||||
ctx.Transaction, ok = h.Runtime.StartTx()
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
if !ok {
|
if !ok {
|
||||||
response.WriteError(w, method)
|
response.WriteError(w, method)
|
||||||
return
|
return
|
||||||
|
|
|
@ -184,6 +184,7 @@ type DocumentStorer interface {
|
||||||
TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error)
|
TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error)
|
||||||
PublicDocuments(ctx domain.RequestContext, orgID string) (documents []doc.SitemapDocument, err error)
|
PublicDocuments(ctx domain.RequestContext, orgID string) (documents []doc.SitemapDocument, err error)
|
||||||
Update(ctx domain.RequestContext, document doc.Document) (err error)
|
Update(ctx domain.RequestContext, document doc.Document) (err error)
|
||||||
|
UpdateRevised(ctx domain.RequestContext, docID string) (err error)
|
||||||
UpdateGroup(ctx domain.RequestContext, document doc.Document) (err error)
|
UpdateGroup(ctx domain.RequestContext, document doc.Document) (err error)
|
||||||
ChangeDocumentSpace(ctx domain.RequestContext, document, space string) (err error)
|
ChangeDocumentSpace(ctx domain.RequestContext, document, space string) (err error)
|
||||||
MoveDocumentSpace(ctx domain.RequestContext, id, move string) (err error)
|
MoveDocumentSpace(ctx domain.RequestContext, id, move string) (err error)
|
||||||
|
|
|
@ -40,9 +40,9 @@ func main() {
|
||||||
// product details
|
// product details
|
||||||
rt.Product = domain.Product{}
|
rt.Product = domain.Product{}
|
||||||
rt.Product.Major = "2"
|
rt.Product.Major = "2"
|
||||||
rt.Product.Minor = "1"
|
rt.Product.Minor = "2"
|
||||||
rt.Product.Patch = "1"
|
rt.Product.Patch = "0"
|
||||||
rt.Product.Revision = "190304203624"
|
rt.Product.Revision = "190313174432"
|
||||||
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
||||||
rt.Product.Edition = domain.CommunityEdition
|
rt.Product.Edition = domain.CommunityEdition
|
||||||
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
|
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
|
||||||
|
|
1576
embed/bindata.go
1576
embed/bindata.go
File diff suppressed because one or more lines are too long
|
@ -63,5 +63,6 @@ module.exports = {
|
||||||
"slug": true,
|
"slug": true,
|
||||||
"iziToast": true,
|
"iziToast": true,
|
||||||
"Papa": true,
|
"Papa": true,
|
||||||
|
"Popper": true,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ module.exports = {
|
||||||
'no-invalid-interactive': false,
|
'no-invalid-interactive': false,
|
||||||
'no-nested-interactive': false,
|
'no-nested-interactive': false,
|
||||||
'no-triple-curlies': false,
|
'no-triple-curlies': false,
|
||||||
|
'no-global-jquery': false,
|
||||||
'style-concatenation': false,
|
'style-concatenation': false,
|
||||||
'simple-unless': false,
|
'simple-unless': false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,8 @@ export default Component.extend(Notifier, Modals, {
|
||||||
meta: meta
|
meta: meta
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.set('newSectionName', '');
|
||||||
|
|
||||||
const promise = this.addSection(model);
|
const promise = this.addSection(model);
|
||||||
promise.then((id) => {
|
promise.then((id) => {
|
||||||
this.set('toEdit', model.page.pageType === 'section' ? id : '');
|
this.set('toEdit', model.page.pageType === 'section' ? id : '');
|
||||||
|
@ -152,6 +154,8 @@ export default Component.extend(Notifier, Modals, {
|
||||||
meta: meta
|
meta: meta
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.set('newSectionName', '');
|
||||||
|
|
||||||
const promise = this.addSection(model);
|
const promise = this.addSection(model);
|
||||||
promise.then((id) => { // eslint-disable-line no-unused-vars
|
promise.then((id) => { // eslint-disable-line no-unused-vars
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,15 +9,18 @@
|
||||||
//
|
//
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import { A } from '@ember/array';
|
import { A } from '@ember/array';
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
|
localStorage: service(),
|
||||||
showDeleteDialog: false,
|
showDeleteDialog: false,
|
||||||
showMoveDialog: false,
|
showMoveDialog: false,
|
||||||
selectedDocuments: A([]),
|
selectedDocuments: A([]),
|
||||||
selectedCaption: 'document',
|
selectedCaption: 'document',
|
||||||
|
viewDensity: "1",
|
||||||
|
|
||||||
showAdd: computed('permissions.documentAdd', 'documents', function() {
|
showAdd: computed('permissions.documentAdd', 'documents', function() {
|
||||||
return this.get('documents.length') === 0 && this.get('permissions.documentAdd');
|
return this.get('documents.length') === 0 && this.get('permissions.documentAdd');
|
||||||
|
@ -36,9 +39,63 @@ export default Component.extend({
|
||||||
let targets = _.reject(this.get('spaces'), {id: space.get('id')});
|
let targets = _.reject(this.get('spaces'), {id: space.get('id')});
|
||||||
this.set('moveOptions', A(targets));
|
this.set('moveOptions', A(targets));
|
||||||
this.set('selectedDocuments', A([]));
|
this.set('selectedDocuments', A([]));
|
||||||
|
|
||||||
|
let sortBy = this.get('localStorage').getSessionItem('space.sortBy');
|
||||||
|
if (!_.isNull(sortBy) && !_.isUndefined(sortBy)) {
|
||||||
|
this.send('onSetSort', sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sortOrder = this.get('localStorage').getSessionItem('space.sortOrder');
|
||||||
|
if (!_.isNull(sortOrder) && !_.isUndefined(sortOrder)) {
|
||||||
|
this.send('onSetSort', sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewDensity = this.get('localStorage').getSessionItem('space.density');
|
||||||
|
if (!_.isNull(viewDensity) && !_.isUndefined(viewDensity)) {
|
||||||
|
this.set('viewDensity', viewDensity);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
onSetSort(val) {
|
||||||
|
switch (val) {
|
||||||
|
case 'name':
|
||||||
|
this.set('sortBy.name', true);
|
||||||
|
this.set('sortBy.created', false);
|
||||||
|
this.set('sortBy.updated', false);
|
||||||
|
break;
|
||||||
|
case 'created':
|
||||||
|
this.set('sortBy.name', false);
|
||||||
|
this.set('sortBy.created', true);
|
||||||
|
this.set('sortBy.updated', false);
|
||||||
|
break;
|
||||||
|
case 'updated':
|
||||||
|
this.set('sortBy.name', false);
|
||||||
|
this.set('sortBy.created', false);
|
||||||
|
this.set('sortBy.updated', true);
|
||||||
|
break;
|
||||||
|
case 'asc':
|
||||||
|
this.set('sortBy.asc', true);
|
||||||
|
this.set('sortBy.desc', false);
|
||||||
|
break;
|
||||||
|
case 'desc':
|
||||||
|
this.set('sortBy.asc', false);
|
||||||
|
this.set('sortBy.desc', true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
onSortBy(attacher) {
|
||||||
|
// attacher.hide();
|
||||||
|
this.get('onFiltered')(this.get('documents'));
|
||||||
|
},
|
||||||
|
|
||||||
|
onSwitchView(v) {
|
||||||
|
this.set('viewDensity', v);
|
||||||
|
this.get('localStorage').storeSessionItem('space.density', v);
|
||||||
|
},
|
||||||
|
|
||||||
onShowDeleteDocuments() {
|
onShowDeleteDocuments() {
|
||||||
this.set('showDeleteDialog', true);
|
this.set('showDeleteDialog', true);
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,7 +28,6 @@ export default Component.extend(AuthMixin, {
|
||||||
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
|
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
|
||||||
}),
|
}),
|
||||||
selectedFilter: '',
|
selectedFilter: '',
|
||||||
spaceLabel: null,
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
@ -56,7 +55,6 @@ export default Component.extend(AuthMixin, {
|
||||||
|
|
||||||
this.set('categories', categories);
|
this.set('categories', categories);
|
||||||
this.set('categoryLinkName', categories.length > 0 ? 'Manage' : 'Add');
|
this.set('categoryLinkName', categories.length > 0 ? 'Manage' : 'Add');
|
||||||
this.set('spaceLabel', _.find(this.get('labels'), {id: this.get('space.labelId')}));
|
|
||||||
|
|
||||||
schedule('afterRender', () => {
|
schedule('afterRender', () => {
|
||||||
if (this.get('categoryFilter') !== '') {
|
if (this.get('categoryFilter') !== '') {
|
||||||
|
@ -120,6 +118,16 @@ export default Component.extend(AuthMixin, {
|
||||||
filtered = this.get('documentsLive');
|
filtered = this.get('documentsLive');
|
||||||
this.set('categoryFilter', '');
|
this.set('categoryFilter', '');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'add':
|
||||||
|
filtered = this.get('recentAdd');
|
||||||
|
this.set('categoryFilter', '');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'update':
|
||||||
|
filtered = this.get('recentUpdate');
|
||||||
|
this.set('categoryFilter', '');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
categories.forEach((cat)=> {
|
categories.forEach((cat)=> {
|
||||||
|
|
|
@ -14,12 +14,4 @@ import Component from '@ember/component';
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
tagName: 'div',
|
tagName: 'div',
|
||||||
classNames: ['master-container'],
|
classNames: ['master-container'],
|
||||||
|
|
||||||
didInsertElement() {
|
|
||||||
this._super(...arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
willDestroyElement() {
|
|
||||||
this._super(...arguments);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,13 +10,23 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
|
localStorage: service('localStorage'),
|
||||||
resultPhrase: '',
|
resultPhrase: '',
|
||||||
searchQuery: computed('keywords', function() {
|
searchQuery: computed('keywords', function() {
|
||||||
return encodeURIComponent(this.get('keywords'));
|
return encodeURIComponent(this.get('keywords'));
|
||||||
}),
|
}),
|
||||||
|
// eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
|
||||||
|
sortBy: {
|
||||||
|
name: true,
|
||||||
|
created: false,
|
||||||
|
updated: false,
|
||||||
|
asc: true,
|
||||||
|
desc: false,
|
||||||
|
},
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
@ -26,7 +36,7 @@ export default Component.extend({
|
||||||
let phrase = 'Nothing found';
|
let phrase = 'Nothing found';
|
||||||
|
|
||||||
if (docs.length > 0) {
|
if (docs.length > 0) {
|
||||||
duped = _.uniq(docs, function (item) {
|
duped = _.uniqBy(docs, function(item) {
|
||||||
return item.get('documentId');
|
return item.get('documentId');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,10 +44,84 @@ export default Component.extend({
|
||||||
let docLabel = duped.length === 1 ? "document" : "documents";
|
let docLabel = duped.length === 1 ? "document" : "documents";
|
||||||
let i = docs.length;
|
let i = docs.length;
|
||||||
let j = duped.length;
|
let j = duped.length;
|
||||||
phrase = `${i} ${references} across ${j} ${docLabel}`;
|
phrase = `${i} ${references} in ${j} ${docLabel}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set('resultPhrase', phrase);
|
this.set('resultPhrase', phrase);
|
||||||
this.set('documents', duped);
|
|
||||||
|
let sortBy = this.get('localStorage').getSessionItem('search.sortBy');
|
||||||
|
if (!_.isNull(sortBy) && !_.isUndefined(sortBy)) {
|
||||||
|
this.send('onSetSort', sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sortOrder = this.get('localStorage').getSessionItem('search.sortOrder');
|
||||||
|
if (!_.isNull(sortOrder) && !_.isUndefined(sortOrder)) {
|
||||||
|
this.send('onSetSort', sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortResults(duped);
|
||||||
|
},
|
||||||
|
|
||||||
|
sortResults(docs) {
|
||||||
|
let ls = this.get('localStorage');
|
||||||
|
let sortBy = this.get('sortBy');
|
||||||
|
|
||||||
|
if (_.isNull(docs)) return;
|
||||||
|
|
||||||
|
if (sortBy.name) {
|
||||||
|
docs = docs.sortBy('document');
|
||||||
|
ls.storeSessionItem('search.sortBy', 'name');
|
||||||
|
}
|
||||||
|
if (sortBy.created) {
|
||||||
|
docs = docs.sortBy('created');
|
||||||
|
ls.storeSessionItem('search.sortBy', 'created');
|
||||||
|
}
|
||||||
|
if (sortBy.updated) {
|
||||||
|
docs = docs.sortBy('revised');
|
||||||
|
ls.storeSessionItem('search.sortBy', 'updated');
|
||||||
|
}
|
||||||
|
if (sortBy.desc) {
|
||||||
|
docs = docs.reverseObjects();
|
||||||
|
ls.storeSessionItem('search.sortOrder', 'desc');
|
||||||
|
} else {
|
||||||
|
ls.storeSessionItem('search.sortOrder', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('documents', docs);
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onSetSort(val) {
|
||||||
|
switch (val) {
|
||||||
|
case 'name':
|
||||||
|
this.set('sortBy.name', true);
|
||||||
|
this.set('sortBy.created', false);
|
||||||
|
this.set('sortBy.updated', false);
|
||||||
|
break;
|
||||||
|
case 'created':
|
||||||
|
this.set('sortBy.name', false);
|
||||||
|
this.set('sortBy.created', true);
|
||||||
|
this.set('sortBy.updated', false);
|
||||||
|
break;
|
||||||
|
case 'updated':
|
||||||
|
this.set('sortBy.name', false);
|
||||||
|
this.set('sortBy.created', false);
|
||||||
|
this.set('sortBy.updated', true);
|
||||||
|
break;
|
||||||
|
case 'asc':
|
||||||
|
this.set('sortBy.asc', true);
|
||||||
|
this.set('sortBy.desc', false);
|
||||||
|
break;
|
||||||
|
case 'desc':
|
||||||
|
this.set('sortBy.asc', false);
|
||||||
|
this.set('sortBy.desc', true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
onSortBy(attacher) {
|
||||||
|
this.sortResults(this.get('documents'));
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,10 +20,6 @@ export default Component.extend({
|
||||||
keywords: '' ,
|
keywords: '' ,
|
||||||
matchFilter: null,
|
matchFilter: null,
|
||||||
|
|
||||||
// init() {
|
|
||||||
// this._super(...arguments);
|
|
||||||
// },
|
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.set('keywords', this.get('filter'));
|
this.set('keywords', this.get('filter'));
|
||||||
|
|
|
@ -9,8 +9,27 @@
|
||||||
//
|
//
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
import AuthMixin from '../../mixins/auth';
|
import AuthMixin from '../../mixins/auth';
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
|
|
||||||
export default Component.extend(AuthMixin, {
|
export default Component.extend(AuthMixin, {
|
||||||
|
localStorage: service(),
|
||||||
|
viewDensity: "1",
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
let viewDensity = this.get('localStorage').getSessionItem('spaces.density');
|
||||||
|
if (!_.isNull(viewDensity) && !_.isUndefined(viewDensity)) {
|
||||||
|
this.set('viewDensity', viewDensity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onSwitchView(v) {
|
||||||
|
this.set('viewDensity', v);
|
||||||
|
this.get('localStorage').storeSessionItem('spaces.density', v);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,6 +21,7 @@ export default Component.extend({
|
||||||
icon: '',
|
icon: '',
|
||||||
color: '',
|
color: '',
|
||||||
light: false,
|
light: false,
|
||||||
|
outline: false,
|
||||||
themed: false,
|
themed: false,
|
||||||
dismiss: false,
|
dismiss: false,
|
||||||
truncate: false,
|
truncate: false,
|
||||||
|
@ -48,6 +49,10 @@ export default Component.extend({
|
||||||
bc += '-light';
|
bc += '-light';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.outline) {
|
||||||
|
bc += '-outline';
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.uppercase) {
|
if (!this.uppercase) {
|
||||||
bc += ' text-case-normal';
|
bc += ' text-case-normal';
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,6 +353,7 @@ let constants = EmberObject.extend({
|
||||||
Send: 'Send',
|
Send: 'Send',
|
||||||
Share: 'Share',
|
Share: 'Share',
|
||||||
SignIn: 'Sign In',
|
SignIn: 'Sign In',
|
||||||
|
Sort: 'Sort',
|
||||||
Unassigned: 'Unassigned',
|
Unassigned: 'Unassigned',
|
||||||
Update: 'Update',
|
Update: 'Update',
|
||||||
Upload: 'Upload',
|
Upload: 'Upload',
|
||||||
|
|
|
@ -67,4 +67,15 @@ export default Model.extend({
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
addRecent: computed('created', function() {
|
||||||
|
let after = moment().subtract(7, 'days');
|
||||||
|
return moment(this.get('created')).isSameOrAfter(after);
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateRecent: computed('created', function() {
|
||||||
|
let after = moment().subtract(7, 'days');
|
||||||
|
return moment(this.get('revised')).isSameOrAfter(after) &&
|
||||||
|
moment(this.get('created')).isBefore(after);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,6 +23,14 @@ export default Controller.extend(NotifierMixin, {
|
||||||
queryParams: ['category'],
|
queryParams: ['category'],
|
||||||
category: '',
|
category: '',
|
||||||
filteredDocs: null,
|
filteredDocs: null,
|
||||||
|
// eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
|
||||||
|
sortBy: {
|
||||||
|
name: true,
|
||||||
|
created: false,
|
||||||
|
updated: false,
|
||||||
|
asc: true,
|
||||||
|
desc: false,
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
onRefresh() {
|
onRefresh() {
|
||||||
|
@ -80,6 +88,30 @@ export default Controller.extend(NotifierMixin, {
|
||||||
},
|
},
|
||||||
|
|
||||||
onFiltered(docs) {
|
onFiltered(docs) {
|
||||||
|
let ls = this.get('localStorage');
|
||||||
|
let sortBy = this.get('sortBy');
|
||||||
|
|
||||||
|
if (_.isNull(docs)) return;
|
||||||
|
|
||||||
|
if (sortBy.name) {
|
||||||
|
docs = docs.sortBy('name');
|
||||||
|
ls.storeSessionItem('space.sortBy', 'name');
|
||||||
|
}
|
||||||
|
if (sortBy.created) {
|
||||||
|
docs = docs.sortBy('created');
|
||||||
|
ls.storeSessionItem('space.sortBy', 'created');
|
||||||
|
}
|
||||||
|
if (sortBy.updated) {
|
||||||
|
docs = docs.sortBy('revised');
|
||||||
|
ls.storeSessionItem('space.sortBy', 'updated');
|
||||||
|
}
|
||||||
|
if (sortBy.desc) {
|
||||||
|
docs = docs.reverseObjects();
|
||||||
|
ls.storeSessionItem('space.sortOrder', 'desc');
|
||||||
|
} else {
|
||||||
|
ls.storeSessionItem('space.sortOrder', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
this.set('filteredDocs', docs);
|
this.set('filteredDocs', docs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
|
|
||||||
import { Promise as EmberPromise, hash } from 'rsvp';
|
import { Promise as EmberPromise, hash } from 'rsvp';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import Route from '@ember/routing/route';
|
|
||||||
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
|
||||||
export default Route.extend(AuthenticatedRouteMixin, {
|
export default Route.extend(AuthenticatedRouteMixin, {
|
||||||
categoryService: service('category'),
|
categoryService: service('category'),
|
||||||
|
@ -37,22 +37,25 @@ export default Route.extend(AuthenticatedRouteMixin, {
|
||||||
let folders = this.modelFor('folder').folders;
|
let folders = this.modelFor('folder').folders;
|
||||||
folders.forEach(f => {
|
folders.forEach(f => {
|
||||||
f.set('selected', false);
|
f.set('selected', false);
|
||||||
})
|
});
|
||||||
|
|
||||||
let documents = this.modelFor('folder').documents;
|
let documents = this.modelFor('folder').documents;
|
||||||
documents.forEach(d => {
|
documents.forEach(d => {
|
||||||
d.set('selected', false);
|
d.set('selected', false);
|
||||||
})
|
});
|
||||||
|
|
||||||
return hash({
|
return hash({
|
||||||
folder: this.modelFor('folder').folder,
|
folder: this.modelFor('folder').folder,
|
||||||
permissions: this.modelFor('folder').permissions,
|
permissions: this.modelFor('folder').permissions,
|
||||||
|
label: _.find(this.modelFor('folder').labels, {id: this.modelFor('folder').folder.get('labelId')}),
|
||||||
labels: this.modelFor('folder').labels,
|
labels: this.modelFor('folder').labels,
|
||||||
folders: folders,
|
folders: folders,
|
||||||
documents: documents,
|
documents: documents,
|
||||||
documentsDraft: _.filter(documents, function(d) { return d.get('lifecycle') === constants.Lifecycle.Draft; }),
|
documentsDraft: _.filter(documents, function(d) { return d.get('lifecycle') === constants.Lifecycle.Draft; }),
|
||||||
documentsLive: _.filter(documents, function(d) { return d.get('lifecycle') === constants.Lifecycle.Live; }),
|
documentsLive: _.filter(documents, function(d) { return d.get('lifecycle') === constants.Lifecycle.Live; }),
|
||||||
templates: this.modelFor('folder').templates,
|
templates: this.modelFor('folder').templates,
|
||||||
|
recentAdd: _.filter(documents, function(d) { return d.get('addRecent'); }),
|
||||||
|
recentUpdate: _.filter(documents, function(d) { return d.get('updateRecent'); }),
|
||||||
showStartDocument: false,
|
showStartDocument: false,
|
||||||
rootDocCount: 0,
|
rootDocCount: 0,
|
||||||
categories: this.get('categories'),
|
categories: this.get('categories'),
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
documents=model.documents
|
documents=model.documents
|
||||||
documentsDraft=model.documentsDraft
|
documentsDraft=model.documentsDraft
|
||||||
documentsLive=model.documentsLive
|
documentsLive=model.documentsLive
|
||||||
|
recentAdd=model.recentAdd
|
||||||
|
recentUpdate=model.recentUpdate
|
||||||
categories=model.categories
|
categories=model.categories
|
||||||
categorySummary=model.categorySummary
|
categorySummary=model.categorySummary
|
||||||
categoryMembers=model.categoryMembers
|
categoryMembers=model.categoryMembers
|
||||||
|
@ -20,6 +22,11 @@
|
||||||
{{#layout/master-content}}
|
{{#layout/master-content}}
|
||||||
<div class="grid-container-6-4">
|
<div class="grid-container-6-4">
|
||||||
<div class="grid-cell-1">
|
<div class="grid-cell-1">
|
||||||
|
{{#if (eq model.folder.labelId "")}}
|
||||||
|
<div class="space-label">Unclassified</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="space-label" style={{{model.label.bgColor}}}>{{model.label.name}}</div>
|
||||||
|
{{/if}}
|
||||||
{{layout/logo-heading
|
{{layout/logo-heading
|
||||||
title=model.folder.name
|
title=model.folder.name
|
||||||
desc=model.folder.desc
|
desc=model.folder.desc
|
||||||
|
@ -37,13 +44,15 @@
|
||||||
onRefresh=(action "onRefresh")}}
|
onRefresh=(action "onRefresh")}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{folder/documents-list
|
{{folder/documents-list
|
||||||
documents=filteredDocs
|
documents=filteredDocs
|
||||||
spaces=model.folders
|
spaces=model.folders
|
||||||
space=model.folder
|
space=model.folder
|
||||||
templates=model.templates
|
templates=model.templates
|
||||||
permissions=model.permissions
|
permissions=model.permissions
|
||||||
|
sortBy=sortBy
|
||||||
|
onFiltered=(action "onFiltered")
|
||||||
onExportDocument=(action "onExportDocument")
|
onExportDocument=(action "onExportDocument")
|
||||||
onDeleteDocument=(action "onDeleteDocument")
|
onDeleteDocument=(action "onDeleteDocument")
|
||||||
onMoveDocument=(action "onMoveDocument")}}
|
onMoveDocument=(action "onMoveDocument")}}
|
||||||
|
|
|
@ -155,6 +155,7 @@ $color-white-dark-1: #f5f5f5;
|
||||||
|
|
||||||
// Documents, spaces card background color
|
// Documents, spaces card background color
|
||||||
$color-card: #F6F4EE;
|
$color-card: #F6F4EE;
|
||||||
|
$color-sidebar: #f2f8fd;
|
||||||
|
|
||||||
/**************************************************************
|
/**************************************************************
|
||||||
* Theme colors.
|
* Theme colors.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// CSS GRID WITH FIXED SIDEBAR OUTSIDE GRID
|
// CSS GRID LAYOUT
|
||||||
|
|
||||||
// Mobile-first layout
|
// Mobile-first layout
|
||||||
.master-container {
|
.master-container {
|
||||||
|
@ -124,7 +124,8 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
z-index: 888;
|
z-index: 888;
|
||||||
background-color: map-get($gray-shades, 100);
|
// background-color: map-get($gray-shades, 100);
|
||||||
|
background-color: $color-sidebar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,6 @@ body {
|
||||||
ul {
|
ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
li {
|
|
||||||
// list-style: none;
|
|
||||||
// list-style-type: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input:-webkit-autofill {
|
input:-webkit-autofill {
|
||||||
|
|
|
@ -266,7 +266,8 @@
|
||||||
.CodeMirror-wrap pre {
|
.CodeMirror-wrap pre {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-linebackground {
|
.CodeMirror-linebackground {
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
@import "ui-button";
|
@import "ui-button";
|
||||||
@import "ui-toolbar";
|
@import "ui-toolbar";
|
||||||
@import "ui-icon-picker";
|
@import "ui-icon-picker";
|
||||||
|
@import "ui-option";
|
|
@ -4,6 +4,9 @@
|
||||||
@mixin button-shadow() {
|
@mixin button-shadow() {
|
||||||
box-shadow: 1px 1px 3px 0px map-get($gray-shades, 500);
|
box-shadow: 1px 1px 3px 0px map-get($gray-shades, 500);
|
||||||
}
|
}
|
||||||
|
@mixin button-shadow-none() {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
%dmz-button {
|
%dmz-button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -153,6 +156,26 @@
|
||||||
background-color: map-get($gray-shades, 300);
|
background-color: map-get($gray-shades, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.dmz-button-gray-outline {
|
||||||
|
@extend %dmz-button;
|
||||||
|
background-color: transparent;
|
||||||
|
color: map-get($gray-shades, 700);
|
||||||
|
border: 1px solid map-get($gray-shades, 300);
|
||||||
|
@extend .no-select;
|
||||||
|
outline: none;
|
||||||
|
@include button-shadow-none();
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
@include button-shadow-none();
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: map-get($gray-shades, 800);
|
||||||
|
border-color: map-get($gray-shades, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dmz-button-theme {
|
.dmz-button-theme {
|
||||||
@extend %dmz-button;
|
@extend %dmz-button;
|
||||||
|
|
47
gui/app/styles/core/ui/ui-option.scss
Normal file
47
gui/app/styles/core/ui/ui-option.scss
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
.ui-option-picker {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
font-size: 0;
|
||||||
|
|
||||||
|
> .option {
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px 11px;
|
||||||
|
color: map-get($gray-shades, 600);
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
> .text {
|
||||||
|
color: map-get($yellow-shades, 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .text {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.00375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .selected {
|
||||||
|
> .text {
|
||||||
|
color: map-get($yellow-shades, 700) !important;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-option-picker-horiz {
|
||||||
|
> .option {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1200px) {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,12 @@
|
||||||
.ember-attacher-popper {
|
.ember-attacher-popper {
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.16), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
-webkit-box-shadow: 3px 3px 33px 0 rgba(0, 0, 0, 0.16), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||||
@include border-radius(3px);
|
box-shadow: 3px 3px 33px 0 rgba(0, 0, 0, 0.16), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
// -webkit-box-shadow: 0 20px 66px 0 rgba(34,48,73,0.2);
|
||||||
|
// box-shadow: 0 20px 66px 0 rgba(34,48,73,0.2);
|
||||||
|
|
||||||
|
@include border-radius(5px);
|
||||||
|
|
||||||
> p {
|
> p {
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
|
@ -31,7 +35,7 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $color-black;
|
color: $color-black;
|
||||||
background-color: map-get($gray-shades, 100);
|
background-color: map-get($yellow-shades, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +88,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .closer {
|
||||||
|
color: map-get($gray-shades, 500);
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 5px 0 0;
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: map-get($gray-shades, 700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .container {
|
||||||
|
padding: 0 20px 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
> .form {
|
> .form {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
|
|
@ -25,14 +25,14 @@
|
||||||
> .index-list {
|
> .index-list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
font-size: 0.9rem;
|
font-size: 1rem;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin: 0 0 0 0;
|
margin: 0 0 0 0;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
@extend .no-select;
|
@extend .no-select;
|
||||||
padding: 4px 0;
|
padding: 5px 0;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
|
|
||||||
> .link {
|
> .link {
|
||||||
color: map-get($gray-shades, 800);
|
color: map-get($gray-shades, 800);
|
||||||
font-weight: 400;
|
font-weight: 500;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: map-get($yellow-shades, 600);
|
color: map-get($yellow-shades, 600);
|
||||||
|
@ -51,6 +51,7 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.wysiwyg {
|
.wysiwyg {
|
||||||
font-size: 15px;
|
font-size: 1rem;
|
||||||
line-height: 20px;
|
line-height: 1.5rem;
|
||||||
color: $color-black-light-1;
|
color: $color-black-light-1;
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
@ -31,20 +31,31 @@
|
||||||
ul {
|
ul {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
padding: 0 0 0 40px;
|
padding: 0 0 0 40px;
|
||||||
line-height: 20px;
|
// line-height: 20px;
|
||||||
|
line-height: 1.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
li {
|
li {
|
||||||
// list-style-type: decimal;
|
// list-style-type: decimal;
|
||||||
line-height: 20px;
|
// line-height: 20px;
|
||||||
|
line-height: 1.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
li {
|
li {
|
||||||
// list-style-type: disc;
|
// list-style-type: disc;
|
||||||
line-height: 20px;
|
// line-height: 20px;
|
||||||
|
line-height: 1.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.5rem;
|
||||||
|
|
||||||
|
> code {
|
||||||
|
line-height: 1.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +94,7 @@
|
||||||
@include border-radius(3px);
|
@include border-radius(3px);
|
||||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||||
color: map-get($gray-shades, 800);
|
color: map-get($gray-shades, 800);
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
|
||||||
> code {
|
> code {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -95,6 +107,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"] {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
background-color: map-get($gray-shades, 100);
|
background-color: map-get($gray-shades, 100);
|
||||||
border-left: 7px solid map-get($gray-shades, 200);
|
border-left: 7px solid map-get($gray-shades, 200);
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
.space-label {
|
||||||
|
@include border-radius(3px);
|
||||||
|
@extend .no-select;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 10px 0 13px 0;
|
||||||
|
padding: 0.3rem 0.7rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
background-color: map-get($gray-shades, 600);
|
||||||
|
color: map-get($gray-shades, 100);
|
||||||
|
}
|
||||||
|
|
||||||
.view-space {
|
.view-space {
|
||||||
> .documents {
|
> .documents {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -5,6 +17,7 @@
|
||||||
|
|
||||||
> .document {
|
> .document {
|
||||||
@include card();
|
@include card();
|
||||||
|
box-shadow: none;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin: 0 0 2rem 0;
|
margin: 0 0 2rem 0;
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
|
|
|
@ -1,22 +1,90 @@
|
||||||
<div class="view-space">
|
<div class="view-space">
|
||||||
|
|
||||||
|
<div class="text-right">
|
||||||
|
{{#ui/ui-toolbar dark=false light=false raised=false large=false bordered=false}}
|
||||||
|
{{ui/ui-toolbar-icon icon=constants.Icon.Blocks color=constants.Color.Gray tooltip="Complete"
|
||||||
|
selected=(eq viewDensity "1") onClick=(action "onSwitchView" "1")}}
|
||||||
|
{{ui/ui-toolbar-icon icon=constants.Icon.All color=constants.Color.Gray tooltip="Comfort"
|
||||||
|
selected=(eq viewDensity "2") onClick=(action "onSwitchView" "2")}}
|
||||||
|
{{ui/ui-toolbar-label label="—" color=constants.Color.Gray tooltip="Compact"
|
||||||
|
selected=(eq viewDensity "3") onClick=(action "onSwitchView" "3")}}
|
||||||
|
{{/ui/ui-toolbar}}
|
||||||
|
|
||||||
|
{{#ui/ui-button
|
||||||
|
light=false
|
||||||
|
outline=true
|
||||||
|
uppercase=false
|
||||||
|
color=constants.Color.Gray
|
||||||
|
label=constants.Label.Sort}}
|
||||||
|
|
||||||
|
{{#attach-popover class="ember-attacher-popper" hideOn="click" showOn="click" isShown=false placement="bottom-end" as |attacher|}}
|
||||||
|
<i class="dicon {{constants.Icon.Cross}} closer" {{action attacher.hide}}/>
|
||||||
|
<div class="container">
|
||||||
|
{{ui/ui-spacer size=100}}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<ul class="ui-option-picker ui-option-picker-horiz">
|
||||||
|
<li class="option {{if sortBy.name "selected"}}" {{action "onSetSort" "name"}}>
|
||||||
|
<div class="text">Name</div>
|
||||||
|
</li>
|
||||||
|
<li class="option {{if sortBy.created "selected"}}" {{action "onSetSort" "created"}}>
|
||||||
|
<div class="text">Created date</div>
|
||||||
|
</li>
|
||||||
|
<li class="option {{if sortBy.updated "selected"}}" {{action "onSetSort" "updated"}}>
|
||||||
|
<div class="text">Last updated</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ui/ui-spacer size=100}}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<ul class="ui-option-picker ui-option-picker-horiz">
|
||||||
|
<li class="option {{if sortBy.asc "selected"}}" {{action "onSetSort" "asc"}}>
|
||||||
|
<div class="text">Ascending</div>
|
||||||
|
</li>
|
||||||
|
<li class="option {{if sortBy.desc "selected"}}" {{action "onSetSort" "desc"}}>
|
||||||
|
<div class="text">Descending</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ui/ui-spacer size=300}}
|
||||||
|
|
||||||
|
{{ui/ui-button
|
||||||
|
light=true
|
||||||
|
color=constants.Color.Yellow
|
||||||
|
label=constants.Label.Sort
|
||||||
|
onClick=(action "onSortBy" attacher)}}
|
||||||
|
</div>
|
||||||
|
{{/attach-popover}}
|
||||||
|
{{/ui/ui-button}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ui/ui-spacer size=200}}
|
||||||
|
|
||||||
<ul class="documents">
|
<ul class="documents">
|
||||||
{{#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 }}</div>
|
||||||
<div class="desc">{{ document.excerpt }}</div>
|
{{#if (not-eq viewDensity "3")}}
|
||||||
|
<div class="desc">{{ document.excerpt }}</div>
|
||||||
|
{{/if}}
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
<div class="meta">
|
{{#if (eq viewDensity "1")}}
|
||||||
<div class="lifecycle">
|
<div class="meta">
|
||||||
<div class="{{if (eq document.lifecycle constants.Lifecycle.Draft) "draft"}}
|
<div class="lifecycle">
|
||||||
{{if (eq document.lifecycle constants.Lifecycle.Live) "live"}}
|
<div class="{{if (eq document.lifecycle constants.Lifecycle.Draft) "draft"}}
|
||||||
{{if (eq document.lifecycle constants.Lifecycle.Archived) "archived"}}">
|
{{if (eq document.lifecycle constants.Lifecycle.Live) "live"}}
|
||||||
{{document.lifecycleLabel}}
|
{{if (eq document.lifecycle constants.Lifecycle.Archived) "archived"}}">
|
||||||
|
{{document.lifecycleLabel}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{folder/document-categories categories=document.category}}
|
||||||
|
{{folder/document-tags documentTags=document.tags}}
|
||||||
</div>
|
</div>
|
||||||
{{folder/document-categories categories=document.category}}
|
{{/if}}
|
||||||
{{folder/document-tags documentTags=document.tags}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if hasDocumentActions}}
|
{{#if hasDocumentActions}}
|
||||||
<div class="checkbox" {{action "selectDocument" document.id}}>
|
<div class="checkbox" {{action "selectDocument" document.id}}>
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
{{ui/ui-spacer size=300}}
|
{{ui/ui-spacer size=300}}
|
||||||
|
|
||||||
<div class="title">label</div>
|
|
||||||
{{#if (eq space.labelId "")}}
|
|
||||||
<div class="label">Unclassified</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="label" style={{{spaceLabel.bgColor}}}>{{spaceLabel.name}}</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{ui/ui-spacer size=200}}
|
|
||||||
|
|
||||||
<div class="title">filter</div>
|
<div class="title">filter</div>
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<div class="item {{if (eq selectedFilter "space") "selected"}}" {{action "onDocumentFilter" "space" space.id}}>
|
<div class="item {{if (eq selectedFilter "space") "selected"}}" {{action "onDocumentFilter" "space" space.id}}>
|
||||||
|
@ -39,6 +30,14 @@
|
||||||
<div class="name">Live ({{documentsLive.length}})</div>
|
<div class="name">Live ({{documentsLive.length}})</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<div class="item {{if (eq selectedFilter "add") "selected"}}" {{action "onDocumentFilter" "add" space.id}}>
|
||||||
|
<i class={{concat "dicon " constants.Icon.Filter}} />
|
||||||
|
<div class="name">Added recently ({{recentAdd.length}})</div>
|
||||||
|
</div>
|
||||||
|
<div class="item {{if (eq selectedFilter "update") "selected"}}" {{action "onDocumentFilter" "update" space.id}}>
|
||||||
|
<i class={{concat "dicon " constants.Icon.Filter}} />
|
||||||
|
<div class="name">Updated recently ({{recentUpdate.length}})</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ui/ui-spacer size=200}}
|
{{ui/ui-spacer size=200}}
|
||||||
|
|
|
@ -18,4 +18,4 @@
|
||||||
{{layout/page-desc desc=desc}}
|
{{layout/page-desc desc=desc}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ui/ui-spacer size=300}}
|
{{ui/ui-spacer size=200}}
|
|
@ -50,7 +50,7 @@
|
||||||
{{#if hasPins}}
|
{{#if hasPins}}
|
||||||
<div class="bookmarks" id="user-pins-button">
|
<div class="bookmarks" id="user-pins-button">
|
||||||
<i class={{concat "dicon " constants.Icon.BookmarkSolid}}></i>
|
<i class={{concat "dicon " constants.Icon.BookmarkSolid}}></i>
|
||||||
{{#attach-popover class="ember-attacher-popper" hideOn="clickout" showOn="click" isShown=false}}
|
{{#attach-popover class="ember-attacher-popper" hideOn="clickout click" showOn="click" isShown=false}}
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
{{#if hasSpacePins}}
|
{{#if hasSpacePins}}
|
||||||
<li class="item header">Spaces</li>
|
<li class="item header">Spaces</li>
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
<div class="update-available-dot" />
|
<div class="update-available-dot" />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#attach-popover class="ember-attacher-popper" hideOn="clickout" showOn="click" isShown=false}}
|
{{#attach-popover class="ember-attacher-popper" hideOn="clickout click" showOn="click" isShown=false}}
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
{{#link-to "profile" class="item"}}Profile{{/link-to}}
|
{{#link-to "profile" class="item"}}Profile{{/link-to}}
|
||||||
{{#if session.isAdmin}}
|
{{#if session.isAdmin}}
|
||||||
|
|
|
@ -1,4 +1,61 @@
|
||||||
<div class="view-search">
|
<div class="view-search">
|
||||||
|
|
||||||
|
{{#if documents}}
|
||||||
|
<div class="text-right">
|
||||||
|
{{#ui/ui-button
|
||||||
|
light=false
|
||||||
|
outline=true
|
||||||
|
uppercase=false
|
||||||
|
color=constants.Color.Gray
|
||||||
|
label=constants.Label.Sort}}
|
||||||
|
|
||||||
|
{{#attach-popover class="ember-attacher-popper" hideOn="click" showOn="click" isShown=false placement="bottom-end" as |attacher|}}
|
||||||
|
<i class="dicon {{constants.Icon.Cross}} closer" {{action attacher.hide}}/>
|
||||||
|
<div class="container">
|
||||||
|
{{ui/ui-spacer size=100}}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<ul class="ui-option-picker ui-option-picker-horiz">
|
||||||
|
<li class="option {{if sortBy.name "selected"}}" {{action "onSetSort" "name"}}>
|
||||||
|
<div class="text">Name</div>
|
||||||
|
</li>
|
||||||
|
<li class="option {{if sortBy.created "selected"}}" {{action "onSetSort" "created"}}>
|
||||||
|
<div class="text">Created date</div>
|
||||||
|
</li>
|
||||||
|
<li class="option {{if sortBy.updated "selected"}}" {{action "onSetSort" "updated"}}>
|
||||||
|
<div class="text">Last updated</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ui/ui-spacer size=100}}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<ul class="ui-option-picker ui-option-picker-horiz">
|
||||||
|
<li class="option {{if sortBy.asc "selected"}}" {{action "onSetSort" "asc"}}>
|
||||||
|
<div class="text">Ascending</div>
|
||||||
|
</li>
|
||||||
|
<li class="option {{if sortBy.desc "selected"}}" {{action "onSetSort" "desc"}}>
|
||||||
|
<div class="text">Descending</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ui/ui-spacer size=300}}
|
||||||
|
|
||||||
|
{{ui/ui-button
|
||||||
|
light=true
|
||||||
|
color=constants.Color.Yellow
|
||||||
|
label=constants.Label.Sort
|
||||||
|
onClick=(action "onSortBy" attacher)}}
|
||||||
|
</div>
|
||||||
|
{{/attach-popover}}
|
||||||
|
{{/ui/ui-button}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ui/ui-spacer size=200}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<div class="result-summary">{{resultPhrase}}</div>
|
<div class="result-summary">{{resultPhrase}}</div>
|
||||||
<ul class="documents">
|
<ul class="documents">
|
||||||
{{#each documents key="id" as |result|}}
|
{{#each documents key="id" as |result|}}
|
||||||
|
@ -17,4 +74,5 @@
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
|
@ -1,4 +1,18 @@
|
||||||
<div class="view-spaces">
|
<div class="view-spaces">
|
||||||
|
|
||||||
|
<div class="text-right">
|
||||||
|
{{#ui/ui-toolbar dark=false light=false raised=false large=false bordered=false}}
|
||||||
|
{{ui/ui-toolbar-icon icon=constants.Icon.Blocks color=constants.Color.Gray tooltip="Complete"
|
||||||
|
selected=(eq viewDensity "1") onClick=(action "onSwitchView" "1")}}
|
||||||
|
{{ui/ui-toolbar-icon icon=constants.Icon.All color=constants.Color.Gray tooltip="Comfort"
|
||||||
|
selected=(eq viewDensity "2") onClick=(action "onSwitchView" "2")}}
|
||||||
|
{{ui/ui-toolbar-label label="—" color=constants.Color.Gray tooltip="Compact"
|
||||||
|
selected=(eq viewDensity "3") onClick=(action "onSwitchView" "3")}}
|
||||||
|
{{/ui/ui-toolbar}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ui/ui-spacer size=200}}
|
||||||
|
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
{{#each spaces as |space|}}
|
{{#each spaces as |space|}}
|
||||||
{{#link-to "folder.index" space.id space.slug}}
|
{{#link-to "folder.index" space.id space.slug}}
|
||||||
|
@ -12,40 +26,47 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{space.name}}
|
{{space.name}}
|
||||||
</div>
|
</div>
|
||||||
<div class="desc">{{space.desc}} </div>
|
{{#if (not-eq viewDensity "3")}}
|
||||||
<div class="meta">
|
<div class="desc">{{space.desc}} </div>
|
||||||
{{!-- {{#if (eq space.spaceType constants.SpaceType.Public)}}
|
{{/if}}
|
||||||
<i class={{concat "dicon " constants.Icon.World}}>
|
{{#if (eq viewDensity "1")}}
|
||||||
{{#attach-tooltip showDelay=1000}}Public space{{/attach-tooltip}}
|
<div class="meta">
|
||||||
</i>
|
{{!-- {{#if (eq space.spaceType constants.SpaceType.Public)}}
|
||||||
{{/if}}
|
<i class={{concat "dicon " constants.Icon.World}}>
|
||||||
{{#if (eq space.spaceType constants.SpaceType.Protected)}}
|
{{#attach-tooltip showDelay=1000}}Public space{{/attach-tooltip}}
|
||||||
<i class={{concat "dicon " constants.Icon.People}}>
|
</i>
|
||||||
{{#attach-tooltip showDelay=1000}}Protected space{{/attach-tooltip}}
|
{{/if}}
|
||||||
</i>
|
{{#if (eq space.spaceType constants.SpaceType.Protected)}}
|
||||||
{{/if}}
|
<i class={{concat "dicon " constants.Icon.People}}>
|
||||||
{{#if (eq space.spaceType constants.SpaceType.Private)}}
|
{{#attach-tooltip showDelay=1000}}Protected space{{/attach-tooltip}}
|
||||||
<i class={{concat "dicon " constants.Icon.Person}}>
|
</i>
|
||||||
{{#attach-tooltip showDelay=1000}}Personal space{{/attach-tooltip}}
|
{{/if}}
|
||||||
</i>
|
{{#if (eq space.spaceType constants.SpaceType.Private)}}
|
||||||
{{/if}} --}}
|
<i class={{concat "dicon " constants.Icon.Person}}>
|
||||||
{{#if space.labelId}}
|
{{#attach-tooltip showDelay=1000}}Personal space{{/attach-tooltip}}
|
||||||
{{spaces/space-label labels=labels labelId=space.labelId}}
|
</i>
|
||||||
{{/if}}
|
{{/if}} --}}
|
||||||
</div>
|
{{#if space.labelId}}
|
||||||
|
{{spaces/space-label labels=labels labelId=space.labelId}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="stats">
|
{{#if (eq viewDensity "1")}}
|
||||||
<div class="stat">
|
<div class="stats">
|
||||||
<div class="number">{{space.countContent}}</div>
|
<div class="stat">
|
||||||
<div class="label">items</div>
|
<div class="number">{{space.countContent}}</div>
|
||||||
|
<div class="label">items</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="number">{{space.countCategory}}</div>
|
||||||
|
<div class="label">categories</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
{{/if}}
|
||||||
<div class="number">{{space.countCategory}}</div>
|
|
||||||
<div class="label">categories</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{#if tooltip}}
|
{{#if tooltip}}
|
||||||
{{#attach-tooltip showDelay=1000}}{{tooltip}}{{/attach-tooltip}}
|
{{#attach-tooltip showDelay=750}}{{tooltip}}{{/attach-tooltip}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{yield}}
|
{{yield}}
|
|
@ -1,4 +1,4 @@
|
||||||
{{label}}
|
{{label}}
|
||||||
{{#if tooltip}}
|
{{#if tooltip}}
|
||||||
{{#attach-tooltip showDelay=1000}}{{tooltip}}{{/attach-tooltip}}
|
{{#attach-tooltip showDelay=750}}{{tooltip}}{{/attach-tooltip}}
|
||||||
{{/if}}
|
{{/if}}
|
|
@ -1,4 +1,4 @@
|
||||||
{{yield}}
|
{{yield}}
|
||||||
{{#if tooltip}}
|
{{#if tooltip}}
|
||||||
{{#attach-tooltip showDelay=1000}}{{tooltip}}{{/attach-tooltip}}
|
{{#attach-tooltip showDelay=750}}{{tooltip}}{{/attach-tooltip}}
|
||||||
{{/if}}
|
{{/if}}
|
|
@ -1,3 +1,3 @@
|
||||||
{{#if tooltip}}
|
{{#if tooltip}}
|
||||||
{{#attach-tooltip showDelay=1000}}{{tooltip}}{{/attach-tooltip}}
|
{{#attach-tooltip showDelay=750}}{{tooltip}}{{/attach-tooltip}}
|
||||||
{{/if}}
|
{{/if}}
|
|
@ -28,7 +28,11 @@ module.exports = function (environment) {
|
||||||
|
|
||||||
// Ember Attacher: tooltips & popover component defaults
|
// Ember Attacher: tooltips & popover component defaults
|
||||||
emberAttacher: {
|
emberAttacher: {
|
||||||
arrow: false
|
arrow: false,
|
||||||
|
animation: 'fill',
|
||||||
|
// popperOptions: {
|
||||||
|
// placement: 'bottom right'
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
|
|
||||||
"ember-cli-mirage": {
|
"ember-cli-mirage": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "documize",
|
"name": "documize",
|
||||||
"version": "2.1.1",
|
"version": "2.2.0",
|
||||||
"description": "The Document IDE",
|
"description": "The Document IDE",
|
||||||
"repository": "",
|
"repository": "",
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
|
|
||||||
package search
|
package search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// QueryOptions defines how we search.
|
// QueryOptions defines how we search.
|
||||||
type QueryOptions struct {
|
type QueryOptions struct {
|
||||||
Keywords string `json:"keywords"`
|
Keywords string `json:"keywords"`
|
||||||
|
@ -23,18 +27,20 @@ type QueryOptions struct {
|
||||||
|
|
||||||
// QueryResult represents 'presentable' search results.
|
// QueryResult represents 'presentable' search results.
|
||||||
type QueryResult struct {
|
type QueryResult struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
OrgID string `json:"orgId"`
|
OrgID string `json:"orgId"`
|
||||||
ItemID string `json:"itemId"`
|
ItemID string `json:"itemId"`
|
||||||
ItemType string `json:"itemType"`
|
ItemType string `json:"itemType"`
|
||||||
DocumentID string `json:"documentId"`
|
DocumentID string `json:"documentId"`
|
||||||
DocumentSlug string `json:"documentSlug"`
|
DocumentSlug string `json:"documentSlug"`
|
||||||
Document string `json:"document"`
|
Document string `json:"document"`
|
||||||
Excerpt string `json:"excerpt"`
|
Excerpt string `json:"excerpt"`
|
||||||
Tags string `json:"tags"`
|
Tags string `json:"tags"`
|
||||||
SpaceID string `json:"spaceId"`
|
SpaceID string `json:"spaceId"`
|
||||||
Space string `json:"space"`
|
Space string `json:"space"`
|
||||||
SpaceSlug string `json:"spaceSlug"`
|
SpaceSlug string `json:"spaceSlug"`
|
||||||
Template bool `json:"template"`
|
Template bool `json:"template"`
|
||||||
VersionID string `json:"versionId"`
|
VersionID string `json:"versionId"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Revised time.Time `json:"revised"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue