1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-23 23:29:42 +02:00

Merge pull request #225 from documize/dev0319

v2.2.0 merge
This commit is contained in:
McMatts 2019-03-15 13:01:28 +00:00 committed by GitHub
commit 6738d2c9e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 1521 additions and 982 deletions

View file

@ -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
View file

@ -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
} }

View file

@ -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)

View file

@ -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
} }

View file

@ -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=?`),

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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})

View file

@ -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

View file

@ -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)

View file

@ -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)

File diff suppressed because one or more lines are too long

View file

@ -63,5 +63,6 @@ module.exports = {
"slug": true, "slug": true,
"iziToast": true, "iziToast": true,
"Papa": true, "Papa": true,
"Popper": true,
} }
}; };

View file

@ -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,
} }

View file

@ -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
}); });

View file

@ -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);
}, },

View file

@ -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)=> {

View file

@ -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);
}
}); });

View file

@ -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'));
},
} }
}); });

View file

@ -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'));

View file

@ -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);
}
}
}); });

View file

@ -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';
} }

View file

@ -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',

View file

@ -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);
})
}); });

View file

@ -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);
} }
} }

View file

@ -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'),

View file

@ -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")}}

View file

@ -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.

View file

@ -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;
} }
} }

View file

@ -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 {

View file

@ -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 {

View file

@ -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";

View file

@ -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;

View 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%;
}
}
}

View file

@ -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;

View file

@ -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;
} }
} }

View file

@ -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);

View file

@ -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;

View file

@ -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}}>

View file

@ -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}}

View file

@ -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}}

View file

@ -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}}

View file

@ -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>

View file

@ -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}}&nbsp;</div> {{#if (not-eq viewDensity "3")}}
<div class="meta"> <div class="desc">{{space.desc}}&nbsp;</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>

View file

@ -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}}

View file

@ -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}}

View file

@ -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}}

View file

@ -1,3 +1,3 @@
{{#if tooltip}} {{#if tooltip}}
{{#attach-tooltip showDelay=1000}}{{tooltip}}{{/attach-tooltip}} {{#attach-tooltip showDelay=750}}{{tooltip}}{{/attach-tooltip}}
{{/if}} {{/if}}

View file

@ -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": {

View file

@ -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",

View file

@ -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"`
} }