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.
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
[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
@ -50,7 +48,7 @@ Heck, Documize will probably run just fine on a Raspberry Pi 3.
## Technology Stack
- Go (v1.12.0)
- Go (v1.12.1)
- Ember JS (v3.8.0)
## Authentication Options

59
core/env/runtime.go vendored
View file

@ -13,25 +13,25 @@
package env
import (
"context"
"database/sql"
"context"
"database/sql"
"github.com/documize/community/domain"
"github.com/documize/community/domain"
"github.com/jmoiron/sqlx"
)
const (
// SiteModeNormal serves app
SiteModeNormal = ""
// SiteModeNormal serves app
SiteModeNormal = ""
// SiteModeOffline serves offline.html
SiteModeOffline = "1"
// SiteModeOffline serves offline.html
SiteModeOffline = "1"
// SiteModeSetup tells Ember to serve setup route
SiteModeSetup = "2"
// SiteModeSetup tells Ember to serve setup route
SiteModeSetup = "2"
// SiteModeBadDB redirects to db-error.html page
SiteModeBadDB = "3"
// SiteModeBadDB redirects to db-error.html page
SiteModeBadDB = "3"
)
// Runtime provides access to database, logger and other server-level scoped objects.
@ -44,40 +44,37 @@ type Runtime struct {
Product domain.Product
}
var ctx = context.Background()
// StartTx beings database transaction with application defined
// database transaction isolation level.
// Any error encountered during this operation is logged to runtime logger.
func (r *Runtime) StartTx() (tx *sqlx.Tx, ok bool) {
tx, err := r.Db.BeginTxx(ctx, &sql.TxOptions{Isolation: sql.LevelReadUncommitted})
if err != nil {
r.Log.Error("unable to start database transaction", err)
return nil, false
}
func (r *Runtime) StartTx(i sql.IsolationLevel) (tx *sqlx.Tx, ok bool) {
tx, err := r.Db.BeginTxx(context.Background(), &sql.TxOptions{Isolation: i})
if err != nil {
r.Log.Error("unable to start database transaction", err)
return nil, false
}
return tx, true
return tx, true
}
// Rollback aborts active database transaction.
// Any error encountered during this operation is logged to runtime logger.
func (r *Runtime) Rollback(tx *sqlx.Tx) bool {
if err := tx.Commit(); err != nil {
r.Log.Error("unable to commit database transaction", err)
return false
}
if err := tx.Commit(); err != nil {
r.Log.Error("unable to commit database transaction", err)
return false
}
return true
return true
}
// Commit flushes pending changes to database.
// Any error encountered during this operation is logged to runtime logger.
func (r *Runtime) Commit(tx *sqlx.Tx) bool {
if err := tx.Commit(); err != nil {
r.Log.Error("unable to commit database transaction", err)
return false
}
if err := tx.Commit(); err != nil {
r.Log.Error("unable to commit database transaction", err)
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.
a, err := h.Store.Attachment.GetAttachment(ctx, ctx.OrgID, request.Param(r, "attachmentID"))
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, request.Param(r, "fileID"))
response.WriteNotFoundError(w, method, request.Param(r, "attachmentID"))
return
}
if err != nil {
@ -161,6 +161,12 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
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
if !canDownload {
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.
if len(username) == 0 || len(password) == 0 {
response.WriteUnauthorizedError(w)
h.Runtime.Log.Info("LDAP authentication aborted due to missing username/password")
return
}
@ -313,6 +314,7 @@ func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
return
}
if !ok {
h.Runtime.Log.Info("LDAP failed login request for " + username + " @ " + dom)
response.WriteUnauthorizedError(w)
return
}

View file

@ -179,6 +179,18 @@ func (s Store) Update(ctx domain.RequestContext, document doc.Document) (err err
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.
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=?`),

View file

@ -175,6 +175,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
ActivityType: activity.TypeCreated})
}
// Update doc revised.
h.Store.Document.UpdateRevised(ctx, doc.RefID)
ctx.Transaction.Commit()
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()
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)
// Update doc revised.
h.Store.Document.UpdateRevised(ctx, doc.RefID)
ctx.Transaction.Commit()
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()
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()
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()
h.Store.Audit.Record(ctx, audit.EventTypeSectionResequence)
@ -969,6 +987,9 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
ActivityType: activity.TypeCreated})
}
// Update doc revised.
h.Store.Document.UpdateRevised(ctx, targetID)
ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeSectionCopy)
@ -1223,6 +1244,9 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
ActivityType: activity.TypeReverted})
}
// Update doc revised.
h.Store.Document.UpdateRevised(ctx, doc.RefID)
ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeSectionRollback)

View file

@ -12,12 +12,14 @@
package search
import (
"database/sql"
"github.com/documize/community/domain"
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/category"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
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
@ -26,20 +28,21 @@ func (m *Indexer) IndexDocument(ctx domain.RequestContext, d doc.Document, a []a
method := "search.IndexDocument"
var err error
ctx.Transaction, err = m.runtime.Db.Beginx()
if err != nil {
m.runtime.Log.Error(method, err)
ok := true
ctx.Transaction, ok = m.runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
m.runtime.Log.Info("unable to start TX for " + method)
return
}
err = m.store.Search.IndexDocument(ctx, d, a)
if err != nil {
ctx.Transaction.Rollback()
m.runtime.Rollback(ctx.Transaction)
m.runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
m.runtime.Commit(ctx.Transaction)
}
// DeleteDocument removes all search entries for document.
@ -47,20 +50,21 @@ func (m *Indexer) DeleteDocument(ctx domain.RequestContext, ID string) {
method := "search.DeleteDocument"
var err error
ctx.Transaction, err = m.runtime.Db.Beginx()
if err != nil {
m.runtime.Log.Error(method, err)
ok := true
ctx.Transaction, ok = m.runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
m.runtime.Log.Info("unable to start TX for " + method)
return
}
err = m.store.Search.DeleteDocument(ctx, ID)
if err != nil {
ctx.Transaction.Rollback()
m.runtime.Rollback(ctx.Transaction)
m.runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
m.runtime.Commit(ctx.Transaction)
}
// 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"
var err error
ctx.Transaction, err = m.runtime.Db.Beginx()
if err != nil {
m.runtime.Log.Error(method, err)
// we do not index pending pages
if p.Status == workflow.ChangePending || p.Status == workflow.ChangePendingNew {
return
}
ok := true
ctx.Transaction, ok = m.runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
m.runtime.Log.Info("unable to start TX for " + method)
return
}
err = m.store.Search.IndexContent(ctx, p)
if err != nil {
ctx.Transaction.Rollback()
m.runtime.Rollback(ctx.Transaction)
m.runtime.Log.Error(method, err)
return
}
err = ctx.Transaction.Commit()
if err != nil {
ctx.Transaction.Rollback()
m.runtime.Log.Error(method, err)
return
}
m.runtime.Commit(ctx.Transaction)
}
// 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"
var err error
ctx.Transaction, err = m.runtime.Db.Beginx()
if err != nil {
m.runtime.Log.Error(method, err)
ok := true
ctx.Transaction, ok = m.runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
m.runtime.Log.Info("unable to start TX for " + method)
return
}
err = m.store.Search.DeleteContent(ctx, pageID)
if err != nil {
ctx.Transaction.Rollback()
m.runtime.Rollback(ctx.Transaction)
m.runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
m.runtime.Commit(ctx.Transaction)
}
// 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/page"
"github.com/documize/community/model/search"
"github.com/documize/community/model/workflow"
"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) {
method := "search.IndexContent"
// we do not index pending pages
if p.Status == workflow.ChangePending || p.Status == workflow.ChangePendingNew {
return
}
// 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'"),
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,
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,
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
dmz_search s,
dmz_doc d
@ -343,7 +337,7 @@ func (s Store) matchLike(ctx domain.RequestContext, keywords, itemType string) (
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,
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
dmz_search s,
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("jira", &jira.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("papertrail", &papertrail.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.
ok := true
ctx.Transaction, ok = h.Runtime.StartTx()
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
response.WriteError(w, method)
return
@ -712,7 +712,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
h.Runtime.Commit(ctx.Transaction)
// Delete data associated with this space.
ctx.Transaction, ok = h.Runtime.StartTx()
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
response.WriteError(w, method)
return
@ -756,7 +756,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
h.Runtime.Commit(ctx.Transaction)
// Record this action.
ctx.Transaction, ok = h.Runtime.StartTx()
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
response.WriteError(w, method)
return

View file

@ -184,6 +184,7 @@ type DocumentStorer interface {
TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error)
PublicDocuments(ctx domain.RequestContext, orgID string) (documents []doc.SitemapDocument, 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)
ChangeDocumentSpace(ctx domain.RequestContext, document, space string) (err error)
MoveDocumentSpace(ctx domain.RequestContext, id, move string) (err error)

View file

@ -40,9 +40,9 @@ func main() {
// product details
rt.Product = domain.Product{}
rt.Product.Major = "2"
rt.Product.Minor = "1"
rt.Product.Patch = "1"
rt.Product.Revision = "190304203624"
rt.Product.Minor = "2"
rt.Product.Patch = "0"
rt.Product.Revision = "190313174432"
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
rt.Product.Edition = domain.CommunityEdition
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,
"iziToast": true,
"Papa": true,
"Popper": true,
}
};

View file

@ -11,6 +11,7 @@ module.exports = {
'no-invalid-interactive': false,
'no-nested-interactive': false,
'no-triple-curlies': false,
'no-global-jquery': false,
'style-concatenation': false,
'simple-unless': false,
}

View file

@ -119,6 +119,8 @@ export default Component.extend(Notifier, Modals, {
meta: meta
};
this.set('newSectionName', '');
const promise = this.addSection(model);
promise.then((id) => {
this.set('toEdit', model.page.pageType === 'section' ? id : '');
@ -152,6 +154,8 @@ export default Component.extend(Notifier, Modals, {
meta: meta
};
this.set('newSectionName', '');
const promise = this.addSection(model);
promise.then((id) => { // eslint-disable-line no-unused-vars
});

View file

@ -9,15 +9,18 @@
//
// https://documize.com
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { A } from '@ember/array';
import Component from '@ember/component';
export default Component.extend({
localStorage: service(),
showDeleteDialog: false,
showMoveDialog: false,
selectedDocuments: A([]),
selectedCaption: 'document',
viewDensity: "1",
showAdd: computed('permissions.documentAdd', 'documents', function() {
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')});
this.set('moveOptions', A(targets));
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() {
this.set('showDeleteDialog', true);
},

View file

@ -28,7 +28,6 @@ export default Component.extend(AuthMixin, {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
selectedFilter: '',
spaceLabel: null,
init() {
this._super(...arguments);
@ -56,7 +55,6 @@ export default Component.extend(AuthMixin, {
this.set('categories', categories);
this.set('categoryLinkName', categories.length > 0 ? 'Manage' : 'Add');
this.set('spaceLabel', _.find(this.get('labels'), {id: this.get('space.labelId')}));
schedule('afterRender', () => {
if (this.get('categoryFilter') !== '') {
@ -120,6 +118,16 @@ export default Component.extend(AuthMixin, {
filtered = this.get('documentsLive');
this.set('categoryFilter', '');
break;
case 'add':
filtered = this.get('recentAdd');
this.set('categoryFilter', '');
break;
case 'update':
filtered = this.get('recentUpdate');
this.set('categoryFilter', '');
break;
}
categories.forEach((cat)=> {

View file

@ -14,12 +14,4 @@ import Component from '@ember/component';
export default Component.extend({
tagName: 'div',
classNames: ['master-container'],
didInsertElement() {
this._super(...arguments);
},
willDestroyElement() {
this._super(...arguments);
}
});

View file

@ -10,13 +10,23 @@
// https://documize.com
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
export default Component.extend({
localStorage: service('localStorage'),
resultPhrase: '',
searchQuery: computed('keywords', function() {
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() {
this._super(...arguments);
@ -26,7 +36,7 @@ export default Component.extend({
let phrase = 'Nothing found';
if (docs.length > 0) {
duped = _.uniq(docs, function (item) {
duped = _.uniqBy(docs, function(item) {
return item.get('documentId');
});
@ -34,10 +44,84 @@ export default Component.extend({
let docLabel = duped.length === 1 ? "document" : "documents";
let i = docs.length;
let j = duped.length;
phrase = `${i} ${references} across ${j} ${docLabel}`;
phrase = `${i} ${references} in ${j} ${docLabel}`;
}
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: '' ,
matchFilter: null,
// init() {
// this._super(...arguments);
// },
didReceiveAttrs() {
this._super(...arguments);
this.set('keywords', this.get('filter'));

View file

@ -9,8 +9,27 @@
//
// https://documize.com
import { inject as service } from '@ember/service';
import AuthMixin from '../../mixins/auth';
import Component from '@ember/component';
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: '',
color: '',
light: false,
outline: false,
themed: false,
dismiss: false,
truncate: false,
@ -48,6 +49,10 @@ export default Component.extend({
bc += '-light';
}
if (this.outline) {
bc += '-outline';
}
if (!this.uppercase) {
bc += ' text-case-normal';
}

View file

@ -353,6 +353,7 @@ let constants = EmberObject.extend({
Send: 'Send',
Share: 'Share',
SignIn: 'Sign In',
Sort: 'Sort',
Unassigned: 'Unassigned',
Update: 'Update',
Upload: 'Upload',

View file

@ -67,4 +67,15 @@ export default Model.extend({
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'],
category: '',
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: {
onRefresh() {
@ -80,6 +88,30 @@ export default Controller.extend(NotifierMixin, {
},
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);
}
}

View file

@ -11,8 +11,8 @@
import { Promise as EmberPromise, hash } from 'rsvp';
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
import Route from '@ember/routing/route';
export default Route.extend(AuthenticatedRouteMixin, {
categoryService: service('category'),
@ -37,22 +37,25 @@ export default Route.extend(AuthenticatedRouteMixin, {
let folders = this.modelFor('folder').folders;
folders.forEach(f => {
f.set('selected', false);
})
});
let documents = this.modelFor('folder').documents;
documents.forEach(d => {
d.set('selected', false);
})
});
return hash({
folder: this.modelFor('folder').folder,
permissions: this.modelFor('folder').permissions,
label: _.find(this.modelFor('folder').labels, {id: this.modelFor('folder').folder.get('labelId')}),
labels: this.modelFor('folder').labels,
folders: folders,
documents: documents,
documentsDraft: _.filter(documents, function(d) { return d.get('lifecycle') === constants.Lifecycle.Draft; }),
documentsLive: _.filter(documents, function(d) { return d.get('lifecycle') === constants.Lifecycle.Live; }),
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,
rootDocCount: 0,
categories: this.get('categories'),

View file

@ -8,6 +8,8 @@
documents=model.documents
documentsDraft=model.documentsDraft
documentsLive=model.documentsLive
recentAdd=model.recentAdd
recentUpdate=model.recentUpdate
categories=model.categories
categorySummary=model.categorySummary
categoryMembers=model.categoryMembers
@ -20,6 +22,11 @@
{{#layout/master-content}}
<div class="grid-container-6-4">
<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
title=model.folder.name
desc=model.folder.desc
@ -37,13 +44,15 @@
onRefresh=(action "onRefresh")}}
</div>
</div>
{{folder/documents-list
documents=filteredDocs
spaces=model.folders
space=model.folder
templates=model.templates
permissions=model.permissions
sortBy=sortBy
onFiltered=(action "onFiltered")
onExportDocument=(action "onExportDocument")
onDeleteDocument=(action "onDeleteDocument")
onMoveDocument=(action "onMoveDocument")}}

View file

@ -155,6 +155,7 @@ $color-white-dark-1: #f5f5f5;
// Documents, spaces card background color
$color-card: #F6F4EE;
$color-sidebar: #f2f8fd;
/**************************************************************
* Theme colors.

View file

@ -1,4 +1,4 @@
// CSS GRID WITH FIXED SIDEBAR OUTSIDE GRID
// CSS GRID LAYOUT
// Mobile-first layout
.master-container {
@ -124,7 +124,8 @@
width: 100%;
padding: 5px 10px;
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 {
margin: 0;
padding: 0;
li {
// list-style: none;
// list-style-type: none;
}
}
input:-webkit-autofill {

View file

@ -266,7 +266,8 @@
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
word-break: normal;
font-size: 1.1rem;
}
.CodeMirror-linebackground {

View file

@ -3,3 +3,4 @@
@import "ui-button";
@import "ui-toolbar";
@import "ui-icon-picker";
@import "ui-option";

View file

@ -4,6 +4,9 @@
@mixin button-shadow() {
box-shadow: 1px 1px 3px 0px map-get($gray-shades, 500);
}
@mixin button-shadow-none() {
box-shadow: none;
}
%dmz-button {
display: inline-block;
@ -153,6 +156,26 @@
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 {
@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 {
background-color: $color-white;
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);
@include border-radius(3px);
-webkit-box-shadow: 3px 3px 33px 0 rgba(0, 0, 0, 0.16), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
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 {
margin: 4px;
@ -31,7 +35,7 @@
&:hover {
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 {
padding: 20px;
width: 300px;

View file

@ -25,14 +25,14 @@
> .index-list {
padding: 0;
list-style: none;
font-size: 0.9rem;
font-size: 1rem;
overflow-x: hidden;
list-style-type: none;
margin: 0 0 0 0;
.item {
@extend .no-select;
padding: 4px 0;
padding: 5px 0;
text-overflow: ellipsis;
word-wrap: break-word;
white-space: nowrap;
@ -40,7 +40,7 @@
> .link {
color: map-get($gray-shades, 800);
font-weight: 400;
font-weight: 500;
&:hover {
color: map-get($yellow-shades, 600);
@ -51,6 +51,7 @@
font-weight: 500;
display: inline-block;
margin-right: 10px;
font-size: 0.9rem;
}
}

View file

@ -1,6 +1,6 @@
.wysiwyg {
font-size: 15px;
line-height: 20px;
font-size: 1rem;
line-height: 1.5rem;
color: $color-black-light-1;
table {
@ -31,20 +31,31 @@
ul {
margin: 15px 0;
padding: 0 0 0 40px;
line-height: 20px;
// line-height: 20px;
line-height: 1.8rem;
}
ol {
li {
// list-style-type: decimal;
line-height: 20px;
// line-height: 20px;
line-height: 1.8rem;
}
}
ul {
li {
// 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);
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
color: map-get($gray-shades, 800);
margin: 1.5rem 0;
> code {
background-color: transparent;
@ -95,6 +107,10 @@
}
}
pre[class*="language-"] {
margin: 1.5rem 0;
}
blockquote {
background-color: map-get($gray-shades, 100);
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 {
> .documents {
margin: 0;
@ -5,6 +17,7 @@
> .document {
@include card();
box-shadow: none;
list-style-type: none;
margin: 0 0 2rem 0;
padding: 15px 20px;

View file

@ -1,22 +1,90 @@
<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">
{{#each documents key="id" as |document|}}
<li class="document {{if document.selected "selected"}}" id="document-{{document.id}}">
{{#link-to "document.index" space.id space.slug document.id document.slug class="info"}}
<div class="name">{{ document.name }}</div>
<div class="desc">{{ document.excerpt }}</div>
{{#if (not-eq viewDensity "3")}}
<div class="desc">{{ document.excerpt }}</div>
{{/if}}
{{/link-to}}
<div class="meta">
<div class="lifecycle">
<div class="{{if (eq document.lifecycle constants.Lifecycle.Draft) "draft"}}
{{if (eq document.lifecycle constants.Lifecycle.Live) "live"}}
{{if (eq document.lifecycle constants.Lifecycle.Archived) "archived"}}">
{{document.lifecycleLabel}}
{{#if (eq viewDensity "1")}}
<div class="meta">
<div class="lifecycle">
<div class="{{if (eq document.lifecycle constants.Lifecycle.Draft) "draft"}}
{{if (eq document.lifecycle constants.Lifecycle.Live) "live"}}
{{if (eq document.lifecycle constants.Lifecycle.Archived) "archived"}}">
{{document.lifecycleLabel}}
</div>
</div>
{{folder/document-categories categories=document.category}}
{{folder/document-tags documentTags=document.tags}}
</div>
{{folder/document-categories categories=document.category}}
{{folder/document-tags documentTags=document.tags}}
</div>
{{/if}}
{{#if hasDocumentActions}}
<div class="checkbox" {{action "selectDocument" document.id}}>

View file

@ -1,14 +1,5 @@
{{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="list">
<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>
{{/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>
{{ui/ui-spacer size=200}}

View file

@ -18,4 +18,4 @@
{{layout/page-desc desc=desc}}
</div>
</div>
{{ui/ui-spacer size=300}}
{{ui/ui-spacer size=200}}

View file

@ -50,7 +50,7 @@
{{#if hasPins}}
<div class="bookmarks" id="user-pins-button">
<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">
{{#if hasSpacePins}}
<li class="item header">Spaces</li>
@ -79,7 +79,7 @@
<div class="update-available-dot" />
{{/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">
{{#link-to "profile" class="item"}}Profile{{/link-to}}
{{#if session.isAdmin}}

View file

@ -1,4 +1,61 @@
<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>
<ul class="documents">
{{#each documents key="id" as |result|}}
@ -17,4 +74,5 @@
</li>
{{/each}}
</ul>
</div>

View file

@ -1,4 +1,18 @@
<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">
{{#each spaces as |space|}}
{{#link-to "folder.index" space.id space.slug}}
@ -12,40 +26,47 @@
{{/if}}
{{space.name}}
</div>
<div class="desc">{{space.desc}}&nbsp;</div>
<div class="meta">
{{!-- {{#if (eq space.spaceType constants.SpaceType.Public)}}
<i class={{concat "dicon " constants.Icon.World}}>
{{#attach-tooltip showDelay=1000}}Public space{{/attach-tooltip}}
</i>
{{/if}}
{{#if (eq space.spaceType constants.SpaceType.Protected)}}
<i class={{concat "dicon " constants.Icon.People}}>
{{#attach-tooltip showDelay=1000}}Protected space{{/attach-tooltip}}
</i>
{{/if}}
{{#if (eq space.spaceType constants.SpaceType.Private)}}
<i class={{concat "dicon " constants.Icon.Person}}>
{{#attach-tooltip showDelay=1000}}Personal space{{/attach-tooltip}}
</i>
{{/if}} --}}
{{#if space.labelId}}
{{spaces/space-label labels=labels labelId=space.labelId}}
{{/if}}
</div>
{{#if (not-eq viewDensity "3")}}
<div class="desc">{{space.desc}}&nbsp;</div>
{{/if}}
{{#if (eq viewDensity "1")}}
<div class="meta">
{{!-- {{#if (eq space.spaceType constants.SpaceType.Public)}}
<i class={{concat "dicon " constants.Icon.World}}>
{{#attach-tooltip showDelay=1000}}Public space{{/attach-tooltip}}
</i>
{{/if}}
{{#if (eq space.spaceType constants.SpaceType.Protected)}}
<i class={{concat "dicon " constants.Icon.People}}>
{{#attach-tooltip showDelay=1000}}Protected space{{/attach-tooltip}}
</i>
{{/if}}
{{#if (eq space.spaceType constants.SpaceType.Private)}}
<i class={{concat "dicon " constants.Icon.Person}}>
{{#attach-tooltip showDelay=1000}}Personal space{{/attach-tooltip}}
</i>
{{/if}} --}}
{{#if space.labelId}}
{{spaces/space-label labels=labels labelId=space.labelId}}
{{/if}}
</div>
{{/if}}
</div>
<div class="stats">
<div class="stat">
<div class="number">{{space.countContent}}</div>
<div class="label">items</div>
{{#if (eq viewDensity "1")}}
<div class="stats">
<div class="stat">
<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 class="stat">
<div class="number">{{space.countCategory}}</div>
<div class="label">categories</div>
</div>
</div>
{{/if}}
</li>
{{/link-to}}
{{/each}}
</ul>
</div>

View file

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

View file

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

View file

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

View file

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

View file

@ -28,7 +28,11 @@ module.exports = function (environment) {
// Ember Attacher: tooltips & popover component defaults
emberAttacher: {
arrow: false
arrow: false,
animation: 'fill',
// popperOptions: {
// placement: 'bottom right'
// }
},
"ember-cli-mirage": {

View file

@ -1,6 +1,6 @@
{
"name": "documize",
"version": "2.1.1",
"version": "2.2.0",
"description": "The Document IDE",
"repository": "",
"license": "AGPL",

View file

@ -11,6 +11,10 @@
package search
import (
"time"
)
// QueryOptions defines how we search.
type QueryOptions struct {
Keywords string `json:"keywords"`
@ -23,18 +27,20 @@ type QueryOptions struct {
// QueryResult represents 'presentable' search results.
type QueryResult struct {
ID string `json:"id"`
OrgID string `json:"orgId"`
ItemID string `json:"itemId"`
ItemType string `json:"itemType"`
DocumentID string `json:"documentId"`
DocumentSlug string `json:"documentSlug"`
Document string `json:"document"`
Excerpt string `json:"excerpt"`
Tags string `json:"tags"`
SpaceID string `json:"spaceId"`
Space string `json:"space"`
SpaceSlug string `json:"spaceSlug"`
Template bool `json:"template"`
VersionID string `json:"versionId"`
ID string `json:"id"`
OrgID string `json:"orgId"`
ItemID string `json:"itemId"`
ItemType string `json:"itemType"`
DocumentID string `json:"documentId"`
DocumentSlug string `json:"documentSlug"`
Document string `json:"document"`
Excerpt string `json:"excerpt"`
Tags string `json:"tags"`
SpaceID string `json:"spaceId"`
Space string `json:"space"`
SpaceSlug string `json:"spaceSlug"`
Template bool `json:"template"`
VersionID string `json:"versionId"`
Created time.Time `json:"created"`
Revised time.Time `json:"revised"`
}