1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-25 16:19:46 +02:00

Merge pull request #189 from documize/core-1118

Documize v2.0
This commit is contained in:
Harvey Kandola 2019-01-18 13:14:56 +00:00 committed by GitHub
commit 7cc0b9c9b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
847 changed files with 47330 additions and 43756 deletions

View file

@ -48,19 +48,13 @@ Integrations for embedding SaaS data within documents, zero add-on/marketplace f
## What does it look like? ## What does it look like?
All spaces.
![Documize](screenshot-1.png "Documize") ![Documize](screenshot-1.png "Documize")
Space view.
![Documize](screenshot-2.png "Documize")
## Latest Release ## Latest Release
[Community Edition: v1.76.2](https://github.com/documize/community/releases) [Community Edition: v2.0.0](https://github.com/documize/community/releases)
[Enterprise Edition: v1.76.2](https://documize.com/downloads) [Enterprise Edition: v2.0.0](https://documize.com/downloads)
## OS support ## OS support
@ -99,8 +93,8 @@ Documize supports the following (evergreen) browsers:
Documize is built with the following technologies: Documize is built with the following technologies:
- EmberJS (v3.1.2) - EmberJS (v3.5.1)
- Go (v1.11.2) - Go (v1.11.4)
## Authentication Options ## Authentication Options

View file

@ -10,12 +10,15 @@ call ember b -o dist-prod/ --environment=production
echo "Copying Ember assets..." echo "Copying Ember assets..."
cd .. cd ..
rd /s /q embed\bindata\public rd /s /q embed\bindata\public
mkdir embed\bindata\public mkdir embed\bindata\public
echo "Copying Ember assets folder" echo "Copying Ember assets folder"
robocopy /e /NFL /NDL /NJH gui\dist-prod\assets embed\bindata\public\assets robocopy /e /NFL /NDL /NJH gui\dist-prod\assets embed\bindata\public\assets
echo "Copying Ember codemirror folder" echo "Copying Ember codemirror folder"
robocopy /e /NFL /NDL /NJH gui\dist-prod\codemirror embed\bindata\public\codemirror robocopy /e /NFL /NDL /NJH gui\dist-prod\codemirror embed\bindata\public\codemirror
echo "Copying Ember prism folder"
robocopy /e /NFL /NDL /NJH gui\dist-prod\prism embed\bindata\public\prism
echo "Copying Ember tinymce folder" echo "Copying Ember tinymce folder"
robocopy /e /NFL /NDL /NJH gui\dist-prod\tinymce embed\bindata\public\tinymce robocopy /e /NFL /NDL /NJH gui\dist-prod\tinymce embed\bindata\public\tinymce
echo "Copying Ember sections folder" echo "Copying Ember sections folder"

View file

@ -8,23 +8,26 @@ echo "Build process started $NOW"
echo "Building Ember assets..." echo "Building Ember assets..."
cd gui cd gui
ember b -o dist-prod/ --environment=production ember build ---environment=production --output-path dist-prod --suppress-sizes true
cd ..
echo "Copying Ember assets..." echo "Copying Ember assets..."
cd ..
rm -rf embed/bindata/public rm -rf embed/bindata/public
mkdir -p embed/bindata/public mkdir -p embed/bindata/public
cp -r gui/dist-prod/assets embed/bindata/public cp -r gui/dist-prod/assets embed/bindata/public
cp -r gui/dist-prod/codemirror embed/bindata/public/codemirror cp -r gui/dist-prod/codemirror embed/bindata/public/codemirror
cp -r gui/dist-prod/tinymce embed/bindata/public/tinymce cp -r gui/dist-prod/prism embed/bindata/public/prism
cp -r gui/dist-prod/sections embed/bindata/public/sections cp -r gui/dist-prod/sections embed/bindata/public/sections
cp -r gui/dist-prod/tinymce embed/bindata/public/tinymce
cp gui/dist-prod/*.* embed/bindata cp gui/dist-prod/*.* embed/bindata
cp gui/dist-prod/favicon.ico embed/bindata/public cp gui/dist-prod/favicon.ico embed/bindata/public
cp gui/dist-prod/manifest.json embed/bindata/public cp gui/dist-prod/manifest.json embed/bindata/public
rm -rf embed/bindata/mail rm -rf embed/bindata/mail
mkdir -p embed/bindata/mail mkdir -p embed/bindata/mail
cp domain/mail/*.html embed/bindata/mail cp domain/mail/*.html embed/bindata/mail
cp core/database/templates/*.html embed/bindata cp core/database/templates/*.html embed/bindata
rm -rf embed/bindata/scripts rm -rf embed/bindata/scripts
mkdir -p embed/bindata/scripts mkdir -p embed/bindata/scripts
mkdir -p embed/bindata/scripts/mysql mkdir -p embed/bindata/scripts/mysql

View file

@ -0,0 +1,37 @@
/* Community Edition */
-- Space labels provide name/color grouping
DROP TABLE IF EXISTS `dmz_space_label`;
CREATE TABLE IF NOT EXISTS `dmz_space_label` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`c_refid` VARCHAR(20) NOT NULL COLLATE utf8_bin,
`c_orgid` VARCHAR(20) NOT NULL COLLATE utf8_bin,
`c_name` VARCHAR(50) NOT NULL DEFAULT '',
`c_color` VARCHAR(10) NOT NULL DEFAULT '',
`c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX `idx_space_label_1` (`id` ASC),
INDEX `idx_space_label_2` (`c_refid` ASC),
INDEX `idx_space_label_3` (`c_orgid` ASC))
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
ENGINE = InnoDB;
-- Space table upgrade to support labelling, icon and summary stats
ALTER TABLE dmz_space ADD COLUMN `c_desc` VARCHAR(200) NOT NULL DEFAULT '' AFTER `c_name`;
ALTER TABLE dmz_space ADD COLUMN `c_labelid` VARCHAR(20) NOT NULL DEFAULT '' COLLATE utf8_bin AFTER `c_likes`;
ALTER TABLE dmz_space ADD COLUMN `c_icon` VARCHAR(20) NOT NULL DEFAULT '' AFTER `c_labelid`;
ALTER TABLE dmz_space ADD COLUMN `c_count_category` INT NOT NULL DEFAULT 0 AFTER `c_icon`;
ALTER TABLE dmz_space ADD COLUMN `c_count_content` INT NOT NULL DEFAULT 0 AFTER `c_count_category`;
-- Org/tenant upgrade to support theming and custom logo
ALTER TABLE dmz_org ADD COLUMN `c_theme` VARCHAR(20) NOT NULL DEFAULT '' AFTER `c_maxtags`;
ALTER TABLE dmz_org ADD COLUMN `c_logo` LONGBLOB AFTER `c_theme`;
-- Populate default values for new fields
UPDATE dmz_space s SET c_count_category=(SELECT COUNT(*) FROM dmz_category WHERE c_spaceid=s.c_refid);
UPDATE dmz_space s SET c_count_content=(SELECT COUNT(*) FROM dmz_doc WHERE c_spaceid=s.c_refid);
-- BUGFIX: Remove zombie group membership records
DELETE FROM dmz_group_member WHERE c_userid NOT IN (SELECT c_userid FROM dmz_user_account);
-- Deprecations

View file

@ -0,0 +1,36 @@
/* Community Edition */
-- Space labels provide name/color grouping
DROP TABLE IF EXISTS dmz_space_label;
CREATE TABLE dmz_space_label (
id bigserial NOT NULL,
c_refid VARCHAR(20) NOT NULL COLLATE ucs_basic,
c_orgid VARCHAR(20) NOT NULL COLLATE ucs_basic,
c_name VARCHAR(50) NOT NULL DEFAULT '',
c_color VARCHAR(10) NOT NULL DEFAULT '',
c_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
c_revised TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_space_label_1 ON dmz_space_label (id);
CREATE INDEX idx_space_label_2 ON dmz_space_label (c_orgid);
-- Space table upgrade to support labelling, icon and summary stats
ALTER TABLE dmz_space ADD COLUMN c_desc VARCHAR(200) NOT NULL DEFAULT '';
ALTER TABLE dmz_space ADD COLUMN c_labelid VARCHAR(20) NOT NULL DEFAULT '' COLLATE ucs_basic;
ALTER TABLE dmz_space ADD COLUMN c_icon VARCHAR(20) NOT NULL DEFAULT '';
ALTER TABLE dmz_space ADD COLUMN c_count_category INT NOT NULL DEFAULT 0;
ALTER TABLE dmz_space ADD COLUMN c_count_content INT NOT NULL DEFAULT 0;
-- Org/tenant upgrade to support theming and custom logo
ALTER TABLE dmz_org ADD COLUMN c_theme VARCHAR(20) NOT NULL DEFAULT '';
ALTER TABLE dmz_org ADD COLUMN c_logo BYTEA;
-- Populate default values for new fields
UPDATE dmz_space s SET c_count_category=(SELECT COUNT(*) FROM dmz_category WHERE c_spaceid=s.c_refid);
UPDATE dmz_space s SET c_count_content=(SELECT COUNT(*) FROM dmz_doc WHERE c_spaceid=s.c_refid);
-- BUGFIX: Remove zombie group membership records
DELETE FROM dmz_group_member WHERE c_userid NOT IN (SELECT c_userid FROM dmz_user_account);
-- Deprecations

View file

@ -14,10 +14,9 @@ package osutil
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"os/exec" "os/exec"
"time" "time"
"github.com/documize/community/core/log"
) )
var errTimeout = errors.New("conversion timelimit exceeded") var errTimeout = errors.New("conversion timelimit exceeded")
@ -39,7 +38,7 @@ func CommandWithTimeout(command *exec.Cmd, timeout time.Duration) ([]byte, error
select { select {
case <-time.After(timeout): case <-time.After(timeout):
if err := command.Process.Kill(); err != nil { if err := command.Process.Kill(); err != nil {
log.Error("failed to kill: ", err) fmt.Errorf("failed to kill: ", err)
} }
<-done // prevent memory leak <-done // prevent memory leak
//fmt.Println("DEBUG timeout") //fmt.Println("DEBUG timeout")

View file

@ -15,9 +15,12 @@ import (
"bytes" "bytes"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/documize/community/domain/auth"
"github.com/documize/community/model/space"
"io" "io"
"mime" "mime"
"net/http" "net/http"
"strings"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/request" "github.com/documize/community/core/request"
@ -47,9 +50,22 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
method := "attachment.Download" method := "attachment.Download"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
ctx.Subdomain = organization.GetSubdomainFromHost(r) ctx.Subdomain = organization.GetSubdomainFromHost(r)
ctx.OrgID = request.Param(r, "orgID")
a, err := h.Store.Attachment.GetAttachment(ctx, request.Param(r, "orgID"), request.Param(r, "attachmentID")) // Is caller permitted to download this attachment?
canDownload := false
// Do e have user authentication token?
authToken := strings.TrimSpace(request.Query(r, "token"))
// Do we have secure sharing token (for external users)?
secureToken := strings.TrimSpace(request.Query(r, "secure"))
// We now fetch attachment, the document and space it lives inside.
// Any data loading issue spells the end of this request.
// Get attachment being requested.
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, "fileID"))
return return
@ -60,6 +76,99 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
return return
} }
// Get the document for this attachment
doc, err := h.Store.Document.Get(ctx, a.DocumentID)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, a.DocumentID)
return
}
if err != nil {
h.Runtime.Log.Error("get attachment document", err)
response.WriteServerError(w, method, err)
return
}
// Get the space for this attachment
sp, err := h.Store.Space.Get(ctx, doc.SpaceID)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, a.DocumentID)
return
}
if err != nil {
h.Runtime.Log.Error("get attachment document", err)
response.WriteServerError(w, method, err)
return
}
// Get the organization for this request
// Get the space for this attachment
org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, a.DocumentID)
return
}
if err != nil {
h.Runtime.Log.Error("get attachment org", err)
response.WriteServerError(w, method, err)
return
}
// At this point, all data associated data is loaded.
// We now begin security checks based upon the request.
// If attachment is in public space then anyone can download
if org.AllowAnonymousAccess && sp.Type == space.ScopePublic {
canDownload = true
}
// External users can be sent secure document viewing links.
// Those documents may contain attachments that external viewers
// can download as required.
// Such secure document viewing links can have expiry dates.
if !canDownload && len(secureToken) > 0 {
canDownload = true
}
// If an user authentication token was provided we check to see
// if user can view document.
// This check only applies to attachments NOT in public spaces.
if !canDownload && len(authToken) > 0 {
// Decode and check incoming token.
creds, _, err := auth.DecodeJWT(h.Runtime, authToken)
if err != nil {
h.Runtime.Log.Error("get attachment decode auth token", err)
response.WriteForbiddenError(w)
return
}
// Check for tampering.
if ctx.OrgID != creds.OrgID {
h.Runtime.Log.Error("get attachment org ID mismatch", err)
response.WriteForbiddenError(w)
return
}
// Use token-based user ID for subsequent processing.
ctx.UserID = creds.UserID
// Check to see if user can view BOTH space and document.
if !permission.CanViewSpace(ctx, *h.Store, sp.RefID) || !permission.CanViewDocument(ctx, *h.Store, a.DocumentID) {
h.Runtime.Log.Error("get attachment cannot view document", err)
response.WriteServerError(w, method, err)
return
}
// Authenticated user can view attachment.
canDownload = true
}
// Send back error if caller unable view attachment
if !canDownload {
h.Runtime.Log.Error("get attachment refused", err)
response.WriteForbiddenError(w)
return
}
// At this point, user can view attachment so we send it back!
typ := mime.TypeByExtension("." + a.Extension) typ := mime.TypeByExtension("." + a.Extension)
if typ == "" { if typ == "" {
typ = "application/octet-stream" typ = "application/octet-stream"

View file

@ -46,7 +46,6 @@ import (
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/group" "github.com/documize/community/model/group"
"github.com/documize/community/model/link" "github.com/documize/community/model/link"
"github.com/documize/community/model/org"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
"github.com/documize/community/model/permission" "github.com/documize/community/model/permission"
"github.com/documize/community/model/pin" "github.com/documize/community/model/pin"
@ -231,14 +230,14 @@ func (b backerHandler) dmzOrg(files *[]backupItem) (err error) {
w = fmt.Sprintf(" WHERE c_refid='%s' ", b.Spec.OrgID) w = fmt.Sprintf(" WHERE c_refid='%s' ", b.Spec.OrgID)
} }
o := []org.Organization{} o := []orgExtended{}
err = b.Runtime.Db.Select(&o, `SELECT id, c_refid AS refid, err = b.Runtime.Db.Select(&o, `SELECT id, c_refid AS refid,
c_title AS title, c_message AS message, c_domain AS domain, c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active, c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_sub,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS subscription, coalesce(c_sub,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS subscription,
coalesce(c_authconfig,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS authconfig, c_maxtags AS maxtags, coalesce(c_authconfig,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS authconfig, c_maxtags AS maxtags,
c_created AS created, c_revised AS revised c_theme AS theme, c_logo AS logo, c_created AS created, c_revised AS revised
FROM dmz_org`+w) FROM dmz_org`+w)
if err != nil { if err != nil {
return return
@ -255,10 +254,6 @@ func (b backerHandler) dmzOrg(files *[]backupItem) (err error) {
// Config, User Config. // Config, User Config.
func (b backerHandler) dmzConfig(files *[]backupItem) (err error) { func (b backerHandler) dmzConfig(files *[]backupItem) (err error) {
type config struct {
ConfigKey string `json:"key"`
ConfigValue string `json:"config"`
}
c := []config{} c := []config{}
err = b.Runtime.Db.Select(&c, `SELECT c_key AS configkey, c_config AS configvalue FROM dmz_config`) err = b.Runtime.Db.Select(&c, `SELECT c_key AS configkey, c_config AS configvalue FROM dmz_config`)
if err != nil { if err != nil {
@ -279,14 +274,7 @@ func (b backerHandler) dmzConfig(files *[]backupItem) (err error) {
w = fmt.Sprintf(" where c_orgid='%s' ", b.Spec.OrgID) w = fmt.Sprintf(" where c_orgid='%s' ", b.Spec.OrgID)
} }
type userConfig struct {
OrgID string `json:"orgId"`
UserID string `json:"userId"`
ConfigKey string `json:"key"`
ConfigValue string `json:"config"`
}
uc := []userConfig{} uc := []userConfig{}
err = b.Runtime.Db.Select(&uc, `select c_orgid AS orgid, c_userid AS userid, err = b.Runtime.Db.Select(&uc, `select c_orgid AS orgid, c_userid AS userid,
c_key AS configkey, c_config AS configvalue FROM dmz_user_config`+w) c_key AS configkey, c_config AS configvalue FROM dmz_user_config`+w)
if err != nil { if err != nil {
@ -475,6 +463,8 @@ func (b backerHandler) dmzSpace(files *[]backupItem) (err error) {
err = b.Runtime.Db.Select(&sp, `SELECT id, c_refid AS refid, err = b.Runtime.Db.Select(&sp, `SELECT id, c_refid AS refid,
c_name AS name, c_orgid AS orgid, c_userid AS userid, c_name AS name, c_orgid AS orgid, c_userid AS userid,
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes, c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
c_icon AS icon, c_labelid AS labelid, c_desc AS description,
c_count_category As countcategory, c_count_content AS countcontent,
c_created AS created, c_revised AS revised c_created AS created, c_revised AS revised
FROM dmz_space`+w) FROM dmz_space`+w)
if err != nil { if err != nil {
@ -671,16 +661,6 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) {
*files = append(*files, backupItem{Filename: "dmz_doc.json", Content: content}) *files = append(*files, backupItem{Filename: "dmz_doc.json", Content: content})
// Vote // Vote
type vote struct {
RefID string `json:"refId"`
OrgID string `json:"orgId"`
DocumentID string `json:"documentId"`
VoterID string `json:"voterId"`
Vote int `json:"vote"`
Created time.Time `json:"created"`
Revised time.Time `json:"revised"`
}
vt := []vote{} vt := []vote{}
err = b.Runtime.Db.Select(&vt, ` err = b.Runtime.Db.Select(&vt, `
SELECT c_refid AS refid, c_orgid AS orgid, SELECT c_refid AS refid, c_orgid AS orgid,
@ -716,16 +696,6 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) {
*files = append(*files, backupItem{Filename: "dmz_doc_link.json", Content: content}) *files = append(*files, backupItem{Filename: "dmz_doc_link.json", Content: content})
// Comment // Comment
type comment struct {
RefID string `json:"feedbackId"`
OrgID string `json:"orgId"`
DocumentID string `json:"documentId"`
UserID string `json:"userId"`
Email string `json:"email"`
Feedback string `json:"feedback"`
Created time.Time `json:"created"`
}
cm := []comment{} cm := []comment{}
err = b.Runtime.Db.Select(&cm, ` err = b.Runtime.Db.Select(&cm, `
SELECT c_refid AS refid, c_orgid AS orgid, c_docid AS documentid, SELECT c_refid AS refid, c_orgid AS orgid, c_docid AS documentid,
@ -743,20 +713,6 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) {
*files = append(*files, backupItem{Filename: "dmz_doc_comment.json", Content: content}) *files = append(*files, backupItem{Filename: "dmz_doc_comment.json", Content: content})
// Share // Share
type share struct {
ID uint64 `json:"id"`
OrgID string `json:"orgId"`
UserID string `json:"userId"`
DocumentID string `json:"documentId"`
Email string `json:"email"`
Message string `json:"message"`
Viewed string `json:"viewed"` // recording each view as |date-viewed|date-viewed|
Secret string `json:"secret"` // secure token used to access document
Expires string `json:"expires"` // number of days from creation, value of 0 means never
Active bool `json:"active"`
Created time.Time `json:"created"`
}
sh := []share{} sh := []share{}
err = b.Runtime.Db.Select(&sh, ` err = b.Runtime.Db.Select(&sh, `
SELECT id AS id, c_orgid AS orgid, c_docid AS documentid, SELECT id AS id, c_orgid AS orgid, c_docid AS documentid,

78
domain/backup/models.go Normal file
View file

@ -0,0 +1,78 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
// Package backup handle data backup/restore to/from ZIP format.
package backup
// Existing data models do not necessarily have fields to hold
// all data when loaded from the database.
// So we extend the existing models to hold additional fields
// for a complete backup and restore process.
import (
"time"
"github.com/documize/community/model/org"
)
type orgExtended struct {
org.Organization
Logo []byte `json:"logo"`
}
type config struct {
ConfigKey string `json:"key"`
ConfigValue string `json:"config"`
}
type userConfig struct {
OrgID string `json:"orgId"`
UserID string `json:"userId"`
ConfigKey string `json:"key"`
ConfigValue string `json:"config"`
}
// Vote
type vote struct {
RefID string `json:"refId"`
OrgID string `json:"orgId"`
DocumentID string `json:"documentId"`
VoterID string `json:"voterId"`
Vote int `json:"vote"`
Created time.Time `json:"created"`
Revised time.Time `json:"revised"`
}
// Comment
type comment struct {
RefID string `json:"feedbackId"`
OrgID string `json:"orgId"`
DocumentID string `json:"documentId"`
UserID string `json:"userId"`
Email string `json:"email"`
Feedback string `json:"feedback"`
Created time.Time `json:"created"`
}
// Share
type share struct {
ID uint64 `json:"id"`
OrgID string `json:"orgId"`
UserID string `json:"userId"`
DocumentID string `json:"documentId"`
Email string `json:"email"`
Message string `json:"message"`
Viewed string `json:"viewed"` // recording each view as |date-viewed|date-viewed|
Secret string `json:"secret"` // secure token used to access document
Expires string `json:"expires"` // number of days from creation, value of 0 means never
Active bool `json:"active"`
Created time.Time `json:"created"`
}

View file

@ -39,7 +39,6 @@ import (
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/group" "github.com/documize/community/model/group"
"github.com/documize/community/model/link" "github.com/documize/community/model/link"
"github.com/documize/community/model/org"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
"github.com/documize/community/model/permission" "github.com/documize/community/model/permission"
"github.com/documize/community/model/pin" "github.com/documize/community/model/pin"
@ -332,7 +331,7 @@ func (r *restoreHandler) readZip(filename string) (found bool, b []byte, err err
func (r *restoreHandler) dmzOrg() (err error) { func (r *restoreHandler) dmzOrg() (err error) {
filename := "dmz_org.json" filename := "dmz_org.json"
org := []org.Organization{} org := []orgExtended{}
err = r.fileJSON(filename, &org) err = r.fileJSON(filename, &org)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename)) err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename))
@ -363,12 +362,14 @@ func (r *restoreHandler) dmzOrg() (err error) {
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(` _, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, INSERT INTO dmz_org (c_refid, c_company, c_title, c_message,
c_domain, c_service, c_email, c_anonaccess, c_authprovider, c_authconfig, c_domain, c_service, c_email, c_anonaccess, c_authprovider, c_authconfig,
c_maxtags, c_verified, c_serial, c_sub, c_active, c_created, c_revised) c_maxtags, c_verified, c_serial, c_sub, c_active,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), c_theme, c_logo, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
org[i].RefID, org[i].Company, org[i].Title, org[i].Message, org[i].RefID, org[i].Company, org[i].Title, org[i].Message,
strings.ToLower(org[i].Domain), org[i].ConversionEndpoint, strings.ToLower(org[i].Email), strings.ToLower(org[i].Domain), org[i].ConversionEndpoint, strings.ToLower(org[i].Email),
org[i].AllowAnonymousAccess, org[i].AuthProvider, org[i].AuthConfig, org[i].AllowAnonymousAccess, org[i].AuthProvider, org[i].AuthConfig,
org[i].MaxTags, true, org[i].Serial, org[i].Subscription, org[i].MaxTags, true, org[i].Serial, org[i].Subscription,
org[i].Theme, org[i].Logo,
org[i].Active, org[i].Created, org[i].Revised) org[i].Active, org[i].Created, org[i].Revised)
if err != nil { if err != nil {
r.Context.Transaction.Rollback() r.Context.Transaction.Rollback()
@ -402,6 +403,7 @@ func (r *restoreHandler) dmzOrg() (err error) {
org[0].Serial = r.Spec.Org.Serial org[0].Serial = r.Spec.Org.Serial
org[0].Title = r.Spec.Org.Title org[0].Title = r.Spec.Org.Title
org[0].Subscription = r.Spec.Org.Subscription org[0].Subscription = r.Spec.Org.Subscription
org[0].Theme = r.Spec.Org.Theme
} }
_, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET _, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET
@ -612,8 +614,16 @@ func (r *restoreHandler) dmzSpace() (err error) {
} }
for i := range sp { for i := range sp {
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind("INSERT INTO dmz_space (c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle, c_likes, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"), _, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
sp[i].RefID, sp[i].Name, r.remapOrg(sp[i].OrgID), r.remapUser(sp[i].UserID), sp[i].Type, sp[i].Lifecycle, sp[i].Likes, sp[i].Created, sp[i].Revised) INSERT INTO dmz_space
(c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle,
c_likes, c_icon, c_desc, c_count_category, c_count_content,
c_labelid, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
sp[i].RefID, sp[i].Name, r.remapOrg(sp[i].OrgID),
r.remapUser(sp[i].UserID), sp[i].Type, sp[i].Lifecycle,
sp[i].Likes, sp[i].Icon, sp[i].Description, sp[i].CountCategory,
sp[i].CountContent, sp[i].LabelID, sp[i].Created, sp[i].Revised)
if err != nil { if err != nil {
r.Context.Transaction.Rollback() r.Context.Transaction.Rollback()

View file

@ -105,6 +105,14 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
err = h.Store.Space.IncrementCategoryCount(ctx, cat.SpaceID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
cat, err = h.Store.Category.Get(ctx, cat.RefID) cat, err = h.Store.Category.Get(ctx, cat.RefID)
@ -295,6 +303,14 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
err = h.Store.Space.DecrementCategoryCount(ctx, cat.SpaceID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeCategoryDelete) h.Store.Audit.Record(ctx, audit.EventTypeCategoryDelete)

View file

@ -50,14 +50,17 @@ func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (c []catego
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
FROM dmz_category FROM dmz_category
WHERE c_orgid=? AND c_spaceid=? AND c_refid IN WHERE c_orgid=? AND c_spaceid=? AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='category' AND c_refid IN (
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='category' SELECT c_refid
FROM dmz_permission
WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='category'
UNION ALL UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid SELECT p.c_refid
WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='category' AND (r.c_userid=? OR r.c_userid='0') FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid
)) WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='category' AND (r.c_userid=? OR r.c_userid='0')
)
ORDER BY name`), ORDER BY name`),
ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID) ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
@ -77,14 +80,17 @@ func (s Store) GetAllBySpace(ctx domain.RequestContext, spaceID string) (c []cat
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
FROM dmz_category FROM dmz_category
WHERE c_orgid=? AND c_spaceid=? AND c_spaceid IN WHERE c_orgid=? AND c_spaceid=? AND c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN (
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view' SELECT c_refid
FROM dmz_permission
WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL UNION ALL
SELECT p.c_refid FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid SELECT p.c_refid
FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid
WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space' AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0') WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space' AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)) )
ORDER BY c_name`), ORDER BY c_name`),
ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID) ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
@ -280,15 +286,19 @@ func (s Store) GetSpaceCategoryMembership(ctx domain.RequestContext, spaceID str
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_categoryid AS categoryid, c_docid AS documentid, c_created AS created, c_revised AS revised SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_categoryid AS categoryid, c_docid AS documentid, c_created AS created, c_revised AS revised
FROM dmz_category_member FROM dmz_category_member
WHERE c_orgid=? AND c_spaceid=? AND c_spaceid IN WHERE c_orgid=? AND c_spaceid=? AND c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN (
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view' SELECT c_refid
FROM dmz_permission
WHERE c_orgid=? AND c_who='user'
AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL UNION ALL
SELECT p.c_refid FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid SELECT p.c_refid
FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid
WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space' WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space'
AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0') AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)) )
ORDER BY documentid`), ORDER BY documentid`),
ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID) ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
@ -306,14 +316,17 @@ func (s Store) GetOrgCategoryMembership(ctx domain.RequestContext, userID string
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_categoryid AS categoryid, c_docid AS documentid, c_created AS created, c_revised AS revised SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_categoryid AS categoryid, c_docid AS documentid, c_created AS created, c_revised AS revised
FROM dmz_category_member FROM dmz_category_member
WHERE c_orgid=? AND c_spaceid IN WHERE c_orgid=? AND c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN (
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view' SELECT c_refid
FROM dmz_permission
WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space' SELECT p.c_refid
AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0') FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid
)) WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space' AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
ORDER BY documentid`), ORDER BY documentid`),
ctx.OrgID, ctx.OrgID, ctx.OrgID, userID, ctx.OrgID, userID) ctx.OrgID, ctx.OrgID, userID, ctx.OrgID, userID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil

View file

@ -248,6 +248,12 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Sto
SourceType: activity.SourceTypeDocument, SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeCreated}) ActivityType: activity.TypeCreated})
err = store.Space.IncrementContentCount(ctx, newDocument.SpaceID)
if err != nil {
err = errors.Wrap(err, "cannot increment space content count")
return
}
err = ctx.Transaction.Commit() err = ctx.Transaction.Commit()
if err != nil { if err != nil {
err = errors.Wrap(err, "cannot commit new document import") err = errors.Wrap(err, "cannot commit new document import")

View file

@ -166,9 +166,6 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
return return
} }
// Sort by title.
sort.Sort(doc.ByName(documents))
// Remove documents that cannot be seen due to lack of // Remove documents that cannot be seen due to lack of
// category view/access permission. // category view/access permission.
cats, err := h.Store.Category.GetBySpace(ctx, spaceID) cats, err := h.Store.Category.GetBySpace(ctx, spaceID)
@ -178,6 +175,30 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
// Keep the latest version when faced with multiple versions. // Keep the latest version when faced with multiple versions.
filtered = FilterLastVersion(filtered) filtered = FilterLastVersion(filtered)
// Sort document list by ID.
sort.Sort(doc.ByID(filtered))
// Attach category membership to each document.
// Put category names into map for easier retrieval.
catNames := make(map[string]string)
for i := range cats {
catNames[cats[i].RefID] = cats[i].Name
}
// Loop the smaller list which is categories assigned to documents.
for i := range members {
// Get name of category
cn := catNames[members[i].CategoryID]
// Find document that is assigned this category.
j := sort.Search(len(filtered), func(k int) bool { return filtered[k].RefID <= members[i].DocumentID })
// Attach category name to document
if j < len(filtered) && filtered[j].RefID == members[i].DocumentID {
filtered[j].Category = append(filtered[j].Category, cn)
}
}
// Sort document list by title.
sort.Sort(doc.ByName(filtered))
response.WriteJSON(w, filtered) response.WriteJSON(w, filtered)
} }
@ -396,6 +417,14 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
ActivityType: activity.TypeDeleted}) ActivityType: activity.TypeDeleted})
} }
err = h.Store.Space.DecrementContentCount(ctx, doc.SpaceID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeDocumentDelete) h.Store.Audit.Record(ctx, audit.EventTypeDocumentDelete)

File diff suppressed because one or more lines are too long

View file

@ -81,17 +81,15 @@ func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
FROM dmz_doc FROM dmz_doc
WHERE c_orgid=? AND c_template=false AND c_spaceid IN WHERE c_orgid=? AND c_template=false AND c_spaceid IN
(SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN (SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_refid IN (SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view' UNION ALL
UNION ALL SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=?
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space' AND p.c_refid=? AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
AND p.c_who='role' AND p.c_location='space' AND p.c_refid=? AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
) )
) )
ORDER BY c_name, c_versionorder`), ORDER BY c_name, c_versionorder`),
ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID) ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
@ -100,6 +98,9 @@ func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
err = errors.Wrap(err, "select documents by space") err = errors.Wrap(err, "select documents by space")
} }
// (SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
// )
return return
} }

View file

@ -179,3 +179,19 @@ func (s Store) GetMembers(ctx domain.RequestContext) (r []group.Record, err erro
return return
} }
// RemoveUserGroups remove user from all group.
func (s Store) RemoveUserGroups(ctx domain.RequestContext, userID string) (err error) {
_, err = s.DeleteWhere(ctx.Transaction,
fmt.Sprintf("DELETE FROM dmz_group_member WHERE c_orgid='%s' AND c_userid='%s'",
ctx.OrgID, userID))
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "RemoveUserGroups")
}
return
}

201
domain/label/endpoint.go Normal file
View file

@ -0,0 +1,201 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package label
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/documize/community/core/env"
"github.com/documize/community/core/request"
"github.com/documize/community/core/response"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/label"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *store.Store
}
// Add space label to the store.
func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
method := "label.Add"
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.IsValid(ctx) {
response.WriteBadLicense(w)
return
}
if !ctx.Administrator {
response.WriteForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
return
}
l := label.Label{}
err = json.Unmarshal(body, &l)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
l.RefID = uniqueid.Generate()
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Label.Add(ctx, l)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeLabelAdd)
response.WriteJSON(w, l)
}
// Get returns all space labels.
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
method := "label.Get"
ctx := domain.GetRequestContext(r)
l, err := h.Store.Label.Get(ctx)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(l) == 0 {
l = []label.Label{}
}
response.WriteJSON(w, l)
}
// Update persists label name/color changes.
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
method := "label.Update"
ctx := domain.GetRequestContext(r)
if !ctx.Administrator {
response.WriteForbiddenError(w)
return
}
labelID := request.Param(r, "labelID")
if len(labelID) == 0 {
response.WriteMissingDataError(w, method, "labelID")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "Bad payload")
return
}
l := label.Label{}
err = json.Unmarshal(body, &l)
if err != nil {
response.WriteBadRequestError(w, method, "Bad payload")
return
}
l.RefID = labelID
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Label.Update(ctx, l)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeLabelUpdate)
response.WriteEmpty(w)
}
// Delete removes space label from store and
// removes label association from spaces.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "label.Delete"
ctx := domain.GetRequestContext(r)
labelID := request.Param(r, "labelID")
if len(labelID) == 0 {
response.WriteMissingDataError(w, method, "labelID")
return
}
var err error
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
return
}
_, err = h.Store.Label.Delete(ctx, labelID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Label.RemoveReference(ctx, labelID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeLabelDelete)
response.WriteEmpty(w)
}

106
domain/label/store.go Normal file
View file

@ -0,0 +1,106 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package label
import (
"database/sql"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/label"
"github.com/pkg/errors"
)
// Store provides data access to section template information.
type Store struct {
store.Context
store.LabelStorer
}
// Add saves space label to store.
func (s Store) Add(ctx domain.RequestContext, l label.Label) (err error) {
l.OrgID = ctx.OrgID
l.Created = time.Now().UTC()
l.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_space_label (c_refid, c_orgid, c_name, c_color, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?)"),
l.RefID, l.OrgID, l.Name, l.Color, l.Created, l.Revised)
if err != nil {
err = errors.Wrap(err, "execute insert label")
}
return
}
// Get returns all space labels from store.
func (s Store) Get(ctx domain.RequestContext) (l []label.Label, err error) {
err = s.Runtime.Db.Select(&l, s.Bind(`
SELECT id, c_refid as refid,
c_orgid as orgid,
c_name AS name, c_color AS color,
c_created AS created, c_revised AS revised
FROM dmz_space_label
WHERE c_orgid=?`),
ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "execute select label")
}
return
}
// Update persists space label changes to the store.
func (s Store) Update(ctx domain.RequestContext, l label.Label) (err error) {
l.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec(s.Bind(`UPDATE dmz_space_label SET
c_name=:name, c_color=:color, c_revised=:revised
WHERE c_orgid=:orgid AND c_refid=:refid`),
l)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "execute update label")
}
return
}
// Delete removes space label from the store.
func (s Store) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
return s.DeleteConstrained(ctx.Transaction, "dmz_space_label", ctx.OrgID, id)
}
// RemoveReference clears space.labelID for given label.
func (s Store) RemoveReference(ctx domain.RequestContext, spaceID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_space SET
c_labelid='', c_revised=?
WHERE c_orgid=? AND c_refid=?`),
time.Now().UTC(), ctx.OrgID, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "execute remove space label reference")
}
return
}

View file

@ -97,7 +97,6 @@ func (h *Handler) GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
if len(files) == 0 { if len(files) == 0 {
files = []attachment.Attachment{} files = []attachment.Attachment{}
} }
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)

View file

@ -76,7 +76,7 @@ background-color: #f6f6f6;
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<a href="{{.URL}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">View document</a> <a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">View document</a>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">

View file

@ -87,7 +87,7 @@ background-color: #f6f6f6;
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<a href="{{.Url}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a> <a href="{{.Url}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">

View file

@ -72,7 +72,7 @@ background-color: #f6f6f6;
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<a href="{{.URL}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a> <a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">

View file

@ -82,7 +82,7 @@ background-color: #f6f6f6;
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<a href="{{.URL}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a> <a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">

View file

@ -79,7 +79,7 @@ background-color: #f6f6f6;
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<a href="{{.URL}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to reset your password</a> <a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to reset your password</a>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">

View file

@ -75,7 +75,7 @@ background-color: #f6f6f6;
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<a href="{{.URL}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Login to Documize</a> <a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Login to Documize</a>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">

View file

@ -81,7 +81,7 @@ background-color: #f6f6f6;
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<a href="{{.URL}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Go to Documize</a> <a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Go to Documize</a>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;"> <tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">

View file

@ -13,6 +13,7 @@ package meta
import ( import (
"bytes" "bytes"
"encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -57,6 +58,7 @@ func (h *Handler) Meta(w http.ResponseWriter, r *http.Request) {
data.AuthProvider = strings.TrimSpace(org.AuthProvider) data.AuthProvider = strings.TrimSpace(org.AuthProvider)
data.AuthConfig = org.AuthConfig data.AuthConfig = org.AuthConfig
data.MaxTags = org.MaxTags data.MaxTags = org.MaxTags
data.Theme = org.Theme
data.Version = h.Runtime.Product.Version data.Version = h.Runtime.Product.Version
data.Revision = h.Runtime.Product.Revision data.Revision = h.Runtime.Product.Revision
data.Edition = h.Runtime.Product.Edition data.Edition = h.Runtime.Product.Edition
@ -111,6 +113,10 @@ Disallow: /auth/*
Disallow: /auth/** Disallow: /auth/**
Disallow: /share Disallow: /share
Disallow: /share/* Disallow: /share/*
Disallow: /attachments
Disallow: /attachments/*
Disallow: /attachment
Disallow: /attachment/*
Sitemap: %s`, sitemap) Sitemap: %s`, sitemap)
} }
@ -190,7 +196,7 @@ func (h *Handler) Reindex(w http.ResponseWriter, r *http.Request) {
if !ctx.GlobalAdmin { if !ctx.GlobalAdmin {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
h.Runtime.Log.Info(fmt.Sprintf("%s attempted search reindex")) h.Runtime.Log.Info(fmt.Sprintf("%s attempted search reindex", ctx.UserID))
return return
} }
@ -253,7 +259,7 @@ func (h *Handler) SearchStatus(w http.ResponseWriter, r *http.Request) {
if !ctx.GlobalAdmin { if !ctx.GlobalAdmin {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
h.Runtime.Log.Info(fmt.Sprintf("%s attempted get of search status")) h.Runtime.Log.Info(fmt.Sprintf("%s attempted get of search status", ctx.UserID))
return return
} }
@ -277,3 +283,72 @@ type sitemapItem struct {
type searchStatus struct { type searchStatus struct {
Entries int `json:"entries"` Entries int `json:"entries"`
} }
// Themes returns list of installed UI themes.
func (h *Handler) Themes(w http.ResponseWriter, r *http.Request) {
type theme struct {
Name string `json:"name"`
Primary string `json:"primary"`
}
th := []theme{}
th = append(th, theme{Name: "", Primary: "#280A42"})
th = append(th, theme{Name: "Brave", Primary: "#BF360C"})
th = append(th, theme{Name: "Conference", Primary: "#176091"})
th = append(th, theme{Name: "Forest", Primary: "#00695C"})
th = append(th, theme{Name: "Harvest", Primary: "#A65F20"})
th = append(th, theme{Name: "Silver", Primary: "#AEBECC"})
th = append(th, theme{Name: "Sunflower", Primary: "#D7B92F"})
response.WriteJSON(w, th)
}
// Logo returns site logo based upon request domain (e.g. acme.documize.com).
// The default Documize logo is returned if organization has not uploaded
// their own logo.
func (h *Handler) Logo(w http.ResponseWriter, r *http.Request) {
ctx := domain.GetRequestContext(r)
d := organization.GetSubdomainFromHost(r)
// If organization has logo, send it back.
logo, err := h.Store.Organization.Logo(ctx, d)
if err == nil && len(logo) > 0 {
h.writeLogo(w, r, logo)
return
}
if err != nil {
h.Runtime.Log.Infof("unable to fetch logo for domain %s", d)
}
// Otherwise, we send back default logo.
h.DefaultLogo(w, r)
}
// DefaultLogo write the default Documize logo to the HTTP response.
func (h *Handler) DefaultLogo(w http.ResponseWriter, r *http.Request) {
logo, err := base64.StdEncoding.DecodeString(defaultLogo)
if err != nil {
h.Runtime.Log.Error("unable to decode default logo", err)
response.WriteEmpty(w)
return
}
h.writeLogo(w, r, logo)
}
// writeLogo writes byte array as logo to HTTP response stream.
func (h *Handler) writeLogo(w http.ResponseWriter, r *http.Request, logo []byte) {
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(logo)))
w.WriteHeader(http.StatusOK)
_, err := w.Write(logo)
if err != nil {
h.Runtime.Log.Error("failed to write logo", err)
return
}
}
var defaultLogo = `
iVBORw0KGgoAAAANSUhEUgAAAEIAAAA2CAYAAABz508/AAAAAXNSR0IArs4c6QAABuRJREFUaAXtW1tsFFUY/s/s7tBCkcpFH0gRfeBBDPCAKAqJJUYSY+DBQIwPRo2JCIZ2W1qiKzIVtkBb2lIlER/UB6KJGE2JCcaQABpNRBQ0QYWYaLxxlRZobbszc36/s9spu9uZ7drZC4WeZDtnzu3//m/+c/nPORWUFgzjnZJu6+L9zDSPiKelZRf8lVlYpPEVQaKHNflbiUY/NRkb/841EJHcYDjSusgmq5OZS4WgCcRUkpxfjDhwSHwUkwR+ihQCLmJbCHFYMHWSHvqw3Qh3+8U2RERVpGUts73bb4OFrS+ukuAQZB4kLWB0bNnw7Wjlx4mo2bRzvi2to2BeH21DxawH65BM1K8xf6LrFB5N1xGGwdqlWPNJIXgOiNCKqZBv2YJiaMPSBO0sD5Y1Gca6nmzb1LpjzQsE8cwxT4LSWFk000SWouaS2fNDbaTljqyJgA0sZkGBbCuMhXJMPAk4K0yWx8ORHQuzwRyEJUwDi6UehWFa8ZHaIzv/ybBWTBYUxB9g5Ow/GKMO8a0205FwpPnJtmhdZya0IAITlBLlHso0TVS6ZxUmFeuHIEueJUnMxKB4J0u5HM8pGByVophKRwwTbZYfY1Z8aFd0w+depdGYdwCIgfatdYe9SxQnJ7ypda6U1mp8xOdAxhSgUF0hUxBYGhxBvXvattScdCs4JmcJpcyuaP3mJQtmzyLSolCsFwuuPjcFnTQ1xdqW+dnG7XsUccPCmCTC0WL16tV2R2PdNl3X7hIsPsV41uvkpT+xWtZA1tT+q5c/QnzYUDCmiXCUbTHqzrdH6x7HuLGXsdR00l2eJchcWP1Ky7r0vBuCCKUUTJ9fb6xfg3GtAW8D6YoOvTPfgnGlwTA+SFlF3zBEOIqiqzRgbfQm3r27CbF+2fr9BaeOet5wRCillsybXYvx4HshNHfLYCqT0oZV7JmoyquQcfpMFMnub7XRVi4tc0Z2pXNTSguGLri54GoQNYzdy7tiPX9CkvtaA6vpLvNyNfIbFRrfRNREdlbYZL8rYyYWXsNHYyUkXwEyuSrSdChAgadbo7V/JMtRDld1pGkrMG3G6rksOU/FVRqWkk8hGifCV9dQnqvN9j5MR8sKTUJCMcZCiZcpDApLIu3a3/LQjDcwcCqP1DWg7uyqaPvtKnNYZdcaHomXqOVueAL3eWQXLFlhUFjSBRrGM/1YPmzETOI+cKpdrz7zMVXPFxFBKQs6JqQrmvyuWTQ9+d2J6zrvT/glTkrSE90DXeQJleKLCKnpxzE6/5vUdHGiCkMweMJNuFpsEclf3fLiaSyXGsahoC8iEiO2CKsNVk9Bec5IyBZht9nDEa1R4D2vRRbqmz3WsQrfs0ZHtP4t7H6fsDVzBSaN+MDjAMj7U/A5jUP726I1RzPJEhp/jW2NPvyGTaVYklumFHN8E6EADALJCCYT0LznieAZkjFY/zBfC6I5CIOu8NU18q5AjgSUlAbPYsBM8S2cpuG1huColN0URGx7ef0FgYMPR/nkJ6beEH43BxEJxdlrQFfGELopLCLZArzi40QMMjNOxCAROZk+w5ualkqbVqLNwq4jiM5hCOxs21L/hZfJZ5vum4iqSPOLts0dxfE+iWxb1ADD+l3ROniaow++ukbYaJ3KJJuLRUJCbbjiwKCwjJ4Gn06XkOZ8LFuLfplEYYhj8cGEL4tgDsGzuz6CXyy+iGh9LfwjNj2+LDYVCoPC4geHLyLUWYKg0Cq4sgfg0Nh+gIyursBdKjqQwJDxYGfE5n3PGu2N4TOQ8qjaGr9i9hT0Ft4tobJ/DOP5nGwM+SbCoXoQUE5AOW0W8umraxQSaL5ljRMxyPA4EeNEpHa2cYsY5COHs8busm6KuR6ypHKfu7dy0i/+n0ulmST7JgJb+TNxkf3tLrPnYZwaFdTCukRMro80HQxQ8FnspP+VSdGR8nwBVwevkq19OFp+pNAkKMXiMiFbYcCBrtte/Uj6D+X7ImLwEHjxUGtFimAXenFVQ8tcP+Jxf1v2w2lxvVkCAZ5H6kro9XQIPCIW4a4jzjQGcObRi4u12s+4iOZiVkgUdCqTyemlk0+AxD4/XyIXdRUGhcWrLaUDrjKfhmMInVMDUvoCJE5p6o4yypnDvUfs/GiBNcrDTK167W37S2u70PYGNwHXSuU7hg+mUW0ci4eouJcc0lel76Th68eg5WnFQdwSOjo6JvxydmAvLqeuwACk/l1Iw+3MB9sb67/zaDslGdeHHpBkrwRjt6Vk5PkF4M/jpLsT14a+ykZUzas7Km2L3gfOyTARHboem6ovwrWASiulS6h7Ar30zfRJNKNb3TbJpvGxVkb9814vXSifRPdiDVKpPno8/AeFjniebez5hgAAAABJRU5ErkJggg==
`

View file

@ -12,8 +12,11 @@
package organization package organization
import ( import (
"bytes"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"github.com/documize/community/model/audit"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -99,3 +102,50 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, org) response.WriteJSON(w, org)
} }
// UploadLogo stores log for organization.
func (h *Handler) UploadLogo(w http.ResponseWriter, r *http.Request) {
method := "organization.UploadLogo"
ctx := domain.GetRequestContext(r)
if !ctx.Administrator {
response.WriteForbiddenError(w)
return
}
// We use default logo if body is empty.
logo := []byte{}
filedata, _, err := r.FormFile("attachment")
if err == nil {
b := new(bytes.Buffer)
_, err = io.Copy(b, filedata)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
} else {
logo = b.Bytes()
}
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Organization.UploadLogo(ctx, logo)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeOrganizationLogo)
response.WriteEmpty(w)
}

View file

@ -50,9 +50,9 @@ func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Or
c_title AS title, c_message AS message, c_domain AS domain, c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active, c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
c_maxtags AS maxtags, c_created AS created, c_revised AS revised c_maxtags AS maxtags, c_theme AS theme, c_created AS created, c_revised AS revised
FROM dmz_org FROM dmz_org
WHERE c_refid=?`), WHERE c_refid=?`),
id) id)
@ -82,9 +82,9 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
c_title AS title, c_message AS message, c_domain AS domain, c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active, c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
c_maxtags AS maxtags, c_created AS created, c_revised AS revised c_maxtags AS maxtags, c_created AS created, c_revised AS revised, c_theme AS theme
FROM dmz_org FROM dmz_org
WHERE c_domain=? AND c_active=true`), WHERE c_domain=? AND c_active=true`),
subdomain) subdomain)
@ -97,9 +97,9 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
c_title AS title, c_message AS message, c_domain AS domain, c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active, c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
c_maxtags AS maxtags, c_created AS created, c_revised AS revised c_maxtags AS maxtags, c_created AS created, c_revised AS revised, c_theme AS theme
FROM dmz_org FROM dmz_org
WHERE c_domain='' AND c_active=true`)) WHERE c_domain='' AND c_active=true`))
@ -116,7 +116,7 @@ func (s Store) UpdateOrganization(ctx domain.RequestContext, org org.Organizatio
_, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET _, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET
c_title=:title, c_message=:message, c_service=:conversionendpoint, c_email=:email, c_title=:title, c_message=:message, c_service=:conversionendpoint, c_email=:email,
c_anonaccess=:allowanonymousaccess, c_maxtags=:maxtags, c_revised=:revised c_anonaccess=:allowanonymousaccess, c_maxtags=:maxtags, c_theme=:theme, c_revised=:revised
WHERE c_refid=:refid`, WHERE c_refid=:refid`,
&org) &org)
@ -176,3 +176,31 @@ func (s Store) CheckDomain(ctx domain.RequestContext, domain string) string {
return "" return ""
} }
// Logo fetchs stored image from store or NULL.
func (s Store) Logo(ctx domain.RequestContext, domain string) (l []byte, err error) {
row := s.Runtime.Db.QueryRow(s.Bind("SELECT c_logo FROM dmz_org WHERE c_domain=? AND c_active=true"), domain)
err = row.Scan(&l)
if err == sql.ErrNoRows {
err = nil
return nil, nil
}
if err != nil {
return nil, err
}
return l, nil
}
// UploadLogo saves custom logo to the organization record.
func (s Store) UploadLogo(ctx domain.RequestContext, logo []byte) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_org SET c_logo=?, c_revised=? WHERE c_refid=?"),
logo, time.Now().UTC(), ctx.OrgID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to save custom logo for org %s", ctx.OrgID))
}
return
}

View file

@ -157,7 +157,6 @@ func (p *Provider) Refresh(ctx *provider.Context, config, data string) (newData
func auth(ctx *provider.Context, store *store.Store, w http.ResponseWriter, r *http.Request) { func auth(ctx *provider.Context, store *store.Store, w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
provider.WriteMessage(w, "gemini", "Bad payload") provider.WriteMessage(w, "gemini", "Bad payload")
return return
@ -165,7 +164,6 @@ func auth(ctx *provider.Context, store *store.Store, w http.ResponseWriter, r *h
var config = geminiConfig{} var config = geminiConfig{}
err = json.Unmarshal(body, &config) err = json.Unmarshal(body, &config)
if err != nil { if err != nil {
provider.WriteMessage(w, "gemini", "Bad payload") provider.WriteMessage(w, "gemini", "Bad payload")
return return
@ -177,30 +175,26 @@ func auth(ctx *provider.Context, store *store.Store, w http.ResponseWriter, r *h
provider.WriteMessage(w, "gemini", "Missing URL value") provider.WriteMessage(w, "gemini", "Missing URL value")
return return
} }
if len(config.Username) == 0 { if len(config.Username) == 0 {
provider.WriteMessage(w, "gemini", "Missing Username value") provider.WriteMessage(w, "gemini", "Missing Username value")
return return
} }
if len(config.APIKey) == 0 { if len(config.APIKey) == 0 {
provider.WriteMessage(w, "gemini", "Missing APIKey value") provider.WriteMessage(w, "gemini", "Missing APIKey value")
return return
} }
creds := []byte(fmt.Sprintf("%s:%s", config.Username, config.APIKey)) creds := []byte(fmt.Sprintf("%s:%s", config.Username, config.APIKey))
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/users/username/%s", config.URL, config.Username), nil) req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/users/username/%s", config.URL, config.Username), nil)
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString(creds)) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString(creds))
client := &http.Client{} client := &http.Client{}
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
provider.WriteError(w, "gemini", err) provider.WriteError(w, "gemini", err)
fmt.Println(err)
return return
} }
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
provider.WriteForbidden(w) provider.WriteForbidden(w)
return return
@ -213,9 +207,9 @@ func auth(ctx *provider.Context, store *store.Store, w http.ResponseWriter, r *h
dec := json.NewDecoder(res.Body) dec := json.NewDecoder(res.Body)
err = dec.Decode(&g) err = dec.Decode(&g)
if err != nil { if err != nil {
provider.WriteError(w, "gemini", err) provider.WriteError(w, "gemini", err)
fmt.Println(err)
return return
} }

View file

@ -324,7 +324,7 @@ const renderTemplate = `
<tr> <tr>
<td class="bordered no-width"><a href="{{ $app }}/browse/{{ $item.Key }}">{{ $item.Key }}&nbsp;</a></td> <td class="bordered no-width"><a href="{{ $app }}/browse/{{ $item.Key }}">{{ $item.Key }}&nbsp;</a></td>
<td class="bordered no-width"><img class="section-jira-icon" src='{{ $item.Fields.Type.IconURL }}' /></td> <td class="bordered no-width"><img class="section-jira-icon" src='{{ $item.Fields.Type.IconURL }}' /></td>
<td class="bordered no-width"><span class="badge badge-warning">{{ $item.Fields.Status.Name }}</span>&nbsp;</td> <td class="bordered no-width"><span class="seciton-jira-status">{{ $item.Fields.Status.Name }}</span>&nbsp;</td>
<td class="bordered no-width"><img class="section-jira-icon" src='{{ $item.Fields.Priority.IconURL }}' /></td> <td class="bordered no-width"><img class="section-jira-icon" src='{{ $item.Fields.Priority.IconURL }}' /></td>
<td class="bordered no-width"> <td class="bordered no-width">
{{range $comp := $item.Fields.Components}} {{range $comp := $item.Fields.Components}}

View file

@ -28,7 +28,7 @@ const renderTemplate = `
<tbody> <tbody>
{{range $item := .Events}} {{range $item := .Events}}
<tr> <tr>
<td class="bordered no-width color-gray">{{ $item.Dated }}</td> <td class="bordered no-width color-gray-600">{{ $item.Dated }}</td>
<td class="bordered no-width">{{ $item.Severity }}</td> <td class="bordered no-width">{{ $item.Severity }}</td>
<td class="bordered width-90">{{ $item.Message }}</td> <td class="bordered width-90">{{ $item.Message }}</td>
</tr> </tr>

View file

@ -99,6 +99,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
var sp space.Space var sp space.Space
sp.Name = model.Name sp.Name = model.Name
sp.Description = model.Description
sp.Icon = model.Icon
sp.LabelID = model.LabelID
sp.RefID = uniqueid.Generate() sp.RefID = uniqueid.Generate()
sp.OrgID = ctx.OrgID sp.OrgID = ctx.OrgID
sp.UserID = ctx.UserID sp.UserID = ctx.UserID
@ -673,77 +676,84 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
// Delete the space first.
ok := true ok := true
ctx.Transaction, ok = h.Runtime.StartTx() ctx.Transaction, ok = h.Runtime.StartTx()
if !ok { if !ok {
response.WriteError(w, method) response.WriteError(w, method)
return return
} }
_, err := h.Store.Document.DeleteBySpace(ctx, id) _, err := h.Store.Space.Delete(ctx, id)
if err != nil { if err != nil {
h.Runtime.Rollback(ctx.Transaction) h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
return
}
h.Runtime.Commit(ctx.Transaction)
// Delete data associated with this space.
ctx.Transaction, ok = h.Runtime.StartTx()
if !ok {
response.WriteError(w, method)
return
}
_, err = h.Store.Document.DeleteBySpace(ctx, id)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
return return
} }
_, err = h.Store.Permission.DeleteSpacePermissions(ctx, id) _, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil { if err != nil {
h.Runtime.Rollback(ctx.Transaction) h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
return return
} }
// remove category permissions
_, err = h.Store.Permission.DeleteSpaceCategoryPermissions(ctx, id) _, err = h.Store.Permission.DeleteSpaceCategoryPermissions(ctx, id)
if err != nil { if err != nil {
h.Runtime.Rollback(ctx.Transaction) h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
return
}
_, err = h.Store.Category.DeleteBySpace(ctx, id)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
return return
} }
_, err = h.Store.Pin.DeletePinnedSpace(ctx, id) _, err = h.Store.Pin.DeletePinnedSpace(ctx, id)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
h.Runtime.Rollback(ctx.Transaction) h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
return return
} }
// remove category and members for space
_, err = h.Store.Category.DeleteBySpace(ctx, id)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
return
}
_, err = h.Store.Space.Delete(ctx, id)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
return
}
// Close out the delete process
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()
if !ok { if !ok {
response.WriteError(w, method) response.WriteError(w, method)
return return
} }
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SpaceID: id, SpaceID: id,
SourceType: activity.SourceTypeSpace, SourceType: activity.SourceTypeSpace,
ActivityType: activity.TypeDeleted}) ActivityType: activity.TypeDeleted})
if err != nil { if err != nil {
h.Runtime.Rollback(ctx.Transaction) h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
} }
h.Runtime.Commit(ctx.Transaction) h.Runtime.Commit(ctx.Transaction)
h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete) h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete)

View file

@ -30,8 +30,15 @@ type Store struct {
// Add adds new folder into the store. // Add adds new folder into the store.
func (s Store) Add(ctx domain.RequestContext, sp space.Space) (err error) { func (s Store) Add(ctx domain.RequestContext, sp space.Space) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_space (c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle, c_likes, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"), _, err = ctx.Transaction.Exec(s.Bind(`
sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Lifecycle, sp.Likes, sp.Created, sp.Revised) INSERT INTO dmz_space
(c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle,
c_likes, c_icon, c_desc, c_count_category, c_count_content,
c_labelid, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Lifecycle, sp.Likes,
sp.Icon, sp.Description, sp.CountCategory, sp.CountContent, sp.LabelID,
sp.Created, sp.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "unable to execute insert for space") err = errors.Wrap(err, "unable to execute insert for space")
@ -45,6 +52,8 @@ func (s Store) Get(ctx domain.RequestContext, id string) (sp space.Space, err er
err = s.Runtime.Db.Get(&sp, s.Bind(`SELECT id, c_refid AS refid, err = s.Runtime.Db.Get(&sp, s.Bind(`SELECT id, c_refid AS refid,
c_name AS name, c_orgid AS orgid, c_userid AS userid, c_name AS name, c_orgid AS orgid, c_userid AS userid,
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes, c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
c_icon AS icon, c_labelid AS labelid, c_desc AS description,
c_count_category As countcategory, c_count_content AS countcontent,
c_created AS created, c_revised AS revised c_created AS created, c_revised AS revised
FROM dmz_space FROM dmz_space
WHERE c_orgid=? and c_refid=?`), WHERE c_orgid=? and c_refid=?`),
@ -62,6 +71,8 @@ func (s Store) PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space
qry := s.Bind(`SELECT id, c_refid AS refid, qry := s.Bind(`SELECT id, c_refid AS refid,
c_name AS name, c_orgid AS orgid, c_userid AS userid, c_name AS name, c_orgid AS orgid, c_userid AS userid,
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes, c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
c_icon AS icon, c_labelid AS labelid, c_desc AS description,
c_count_category AS countcategory, c_count_content AS countcontent,
c_created AS created, c_revised AS revised c_created AS created, c_revised AS revised
FROM dmz_space FROM dmz_space
WHERE c_orgid=? AND c_type=1`) WHERE c_orgid=? AND c_type=1`)
@ -85,6 +96,8 @@ func (s Store) GetViewable(ctx domain.RequestContext) (sp []space.Space, err err
q := s.Bind(`SELECT id, c_refid AS refid, q := s.Bind(`SELECT id, c_refid AS refid,
c_name AS name, c_orgid AS orgid, c_userid AS userid, c_name AS name, c_orgid AS orgid, c_userid AS userid,
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes, c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
c_icon AS icon, c_labelid AS labelid, c_desc AS description,
c_count_category AS countcategory, c_count_content AS countcontent,
c_created AS created, c_revised AS revised c_created AS created, c_revised AS revised
FROM dmz_space FROM dmz_space
WHERE c_orgid=? AND c_refid IN WHERE c_orgid=? AND c_refid IN
@ -122,14 +135,18 @@ func (s Store) AdminList(ctx domain.RequestContext) (sp []space.Space, err error
SELECT id, c_refid AS refid, SELECT id, c_refid AS refid,
c_name AS name, c_orgid AS orgid, c_userid AS userid, c_name AS name, c_orgid AS orgid, c_userid AS userid,
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes, c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
c_created AS created, c_revised AS revised c_created AS created, c_revised AS revised,
c_icon AS icon, c_labelid AS labelid, c_desc AS description,
c_count_category AS countcategory, c_count_content AS countcontent
FROM dmz_space FROM dmz_space
WHERE c_orgid=? AND (c_type=? OR c_type=?) WHERE c_orgid=? AND (c_type=? OR c_type=?)
UNION ALL UNION ALL
SELECT id, c_refid AS refid, SELECT id, c_refid AS refid,
c_name AS name, c_orgid AS orgid, c_userid AS userid, c_name AS name, c_orgid AS orgid, c_userid AS userid,
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes, c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
c_created AS created, c_revised AS revised c_created AS created, c_revised AS revised,
c_icon AS icon, c_labelid AS labelid, c_desc AS description,
c_count_category AS countcategory, c_count_content AS countcontent
FROM dmz_space FROM dmz_space
WHERE c_orgid=? AND (c_type=? OR c_type=?) AND c_refid NOT IN WHERE c_orgid=? AND (c_type=? OR c_type=?) AND c_refid NOT IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_action='own') (SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_action='own')
@ -154,7 +171,13 @@ func (s Store) AdminList(ctx domain.RequestContext) (sp []space.Space, err error
func (s Store) Update(ctx domain.RequestContext, sp space.Space) (err error) { func (s Store) Update(ctx domain.RequestContext, sp space.Space) (err error) {
sp.Revised = time.Now().UTC() sp.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec("UPDATE dmz_space SET c_name=:name, c_type=:type, c_lifecycle=:lifecycle, c_userid=:userid, c_likes=:likes, c_revised=:revised WHERE c_orgid=:orgid AND c_refid=:refid", &sp) _, err = ctx.Transaction.NamedExec(`
UPDATE dmz_space
SET c_name=:name, c_type=:type, c_lifecycle=:lifecycle, c_userid=:userid,
c_likes=:likes, c_desc=:description, c_labelid=:labelid, c_icon=:icon,
c_count_category=:countcategory, c_count_content=:countcontent,
c_revised=:revised
WHERE c_orgid=:orgid AND c_refid=:refid`, &sp)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for space %s", sp.RefID)) err = errors.Wrap(err, fmt.Sprintf("unable to execute update for space %s", sp.RefID))
} }
@ -166,3 +189,55 @@ func (s Store) Update(ctx domain.RequestContext, sp space.Space) (err error) {
func (s Store) Delete(ctx domain.RequestContext, id string) (rows int64, err error) { func (s Store) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
return s.DeleteConstrained(ctx.Transaction, "dmz_space", ctx.OrgID, id) return s.DeleteConstrained(ctx.Transaction, "dmz_space", ctx.OrgID, id)
} }
// IncrementCategoryCount increments usage counter for space category.
func (s Store) IncrementCategoryCount(ctx domain.RequestContext, spaceID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_space SET
c_count_category=c_count_category+1, c_revised=? WHERE c_orgid=? AND c_refid=?`),
time.Now().UTC(), ctx.OrgID, spaceID)
if err != nil {
err = errors.Wrap(err, "execute increment category count")
}
return
}
// DecrementCategoryCount decrements usage counter for space category.
func (s Store) DecrementCategoryCount(ctx domain.RequestContext, spaceID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_space SET
c_count_category=c_count_category-1, c_revised=? WHERE c_orgid=? AND c_refid=?`),
time.Now().UTC(), ctx.OrgID, spaceID)
if err != nil {
err = errors.Wrap(err, "execute decrement category count")
}
return
}
// IncrementContentCount increments usage counter for space category.
func (s Store) IncrementContentCount(ctx domain.RequestContext, spaceID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_space SET
c_count_content=c_count_content+1, c_revised=? WHERE c_orgid=? AND c_refid=?`),
time.Now().UTC(), ctx.OrgID, spaceID)
if err != nil {
err = errors.Wrap(err, "execute increment content count")
}
return
}
// DecrementContentCount decrements usage counter for space category.
func (s Store) DecrementContentCount(ctx domain.RequestContext, spaceID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_space SET
c_count_content=c_count_content-1, c_revised=? WHERE c_orgid=? AND c_refid=?`),
time.Now().UTC(), ctx.OrgID, spaceID)
if err != nil {
err = errors.Wrap(err, "execute decrement category count")
}
return
}

View file

@ -21,6 +21,7 @@ import (
"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/group" "github.com/documize/community/model/group"
"github.com/documize/community/model/label"
"github.com/documize/community/model/link" "github.com/documize/community/model/link"
"github.com/documize/community/model/org" "github.com/documize/community/model/org"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
@ -42,6 +43,7 @@ type Store struct {
Document DocumentStorer Document DocumentStorer
Group GroupStorer Group GroupStorer
Link LinkStorer Link LinkStorer
Label LabelStorer
Meta MetaStorer Meta MetaStorer
Organization OrganizationStorer Organization OrganizationStorer
Page PageStorer Page PageStorer
@ -62,6 +64,10 @@ type SpaceStorer interface {
Update(ctx domain.RequestContext, sp space.Space) (err error) Update(ctx domain.RequestContext, sp space.Space) (err error)
Delete(ctx domain.RequestContext, id string) (rows int64, err error) Delete(ctx domain.RequestContext, id string) (rows int64, err error)
AdminList(ctx domain.RequestContext) (sp []space.Space, err error) AdminList(ctx domain.RequestContext) (sp []space.Space, err error)
IncrementCategoryCount(ctx domain.RequestContext, spaceID string) (err error)
DecrementCategoryCount(ctx domain.RequestContext, spaceID string) (err error)
IncrementContentCount(ctx domain.RequestContext, spaceID string) (err error)
DecrementContentCount(ctx domain.RequestContext, spaceID string) (err error)
} }
// CategoryStorer defines required methods for category and category membership management // CategoryStorer defines required methods for category and category membership management
@ -148,6 +154,8 @@ type OrganizationStorer interface {
RemoveOrganization(ctx domain.RequestContext, orgID string) (err error) RemoveOrganization(ctx domain.RequestContext, orgID string) (err error)
UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error)
CheckDomain(ctx domain.RequestContext, domain string) string CheckDomain(ctx domain.RequestContext, domain string) string
Logo(ctx domain.RequestContext, domain string) (l []byte, err error)
UploadLogo(ctx domain.RequestContext, l []byte) (err error)
} }
// PinStorer defines required methods for pin management // PinStorer defines required methods for pin management
@ -285,6 +293,7 @@ type GroupStorer interface {
GetMembers(ctx domain.RequestContext) (r []group.Record, err error) GetMembers(ctx domain.RequestContext) (r []group.Record, err error)
JoinGroup(ctx domain.RequestContext, groupID, userID string) (err error) JoinGroup(ctx domain.RequestContext, groupID, userID string) (err error)
LeaveGroup(ctx domain.RequestContext, groupID, userID string) (err error) LeaveGroup(ctx domain.RequestContext, groupID, userID string) (err error)
RemoveUserGroups(ctx domain.RequestContext, userID string) (err error)
} }
// MetaStorer provide specialist methods for global administrators. // MetaStorer provide specialist methods for global administrators.
@ -295,3 +304,12 @@ type MetaStorer interface {
Attachments(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error) Attachments(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error)
SearchIndexCount(ctx domain.RequestContext) (c int, err error) SearchIndexCount(ctx domain.RequestContext) (c int, err error)
} }
// LabelStorer defines required methods for space label management
type LabelStorer interface {
Add(ctx domain.RequestContext, l label.Label) (err error)
Get(ctx domain.RequestContext) (l []label.Label, err error)
Update(ctx domain.RequestContext, l label.Label) (err error)
Delete(ctx domain.RequestContext, id string) (rows int64, err error)
RemoveReference(ctx domain.RequestContext, spaceID string) (err error)
}

View file

@ -36,7 +36,7 @@ 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"
pm "github.com/documize/community/model/permission" pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/template" // "github.com/documize/community/model/template"
"github.com/documize/community/model/workflow" "github.com/documize/community/model/workflow"
uuid "github.com/nu7hatch/gouuid" uuid "github.com/nu7hatch/gouuid"
) )
@ -66,23 +66,23 @@ func (h *Handler) SavedList(w http.ResponseWriter, r *http.Request) {
return return
} }
templates := []template.Template{} // templates := []template.Template{}
for _, d := range documents { // for _, d := range documents {
var t = template.Template{} // var t = template.Template{}
t.ID = d.RefID // t.ID = d.RefID
t.Title = d.Name // t.Title = d.Name
t.Description = d.Excerpt // t.Description = d.Excerpt
t.Author = "" // t.Author = ""
t.Dated = d.Created // t.Dated = d.Created
t.Type = template.TypePrivate // t.Type = template.TypePrivate
if d.SpaceID == spaceID { // if d.SpaceID == spaceID {
templates = append(templates, t) // templates = append(templates, t)
} // }
} // }
response.WriteJSON(w, templates) response.WriteJSON(w, documents)
} }
// SaveAs saves existing document as a template. // SaveAs saves existing document as a template.
@ -245,6 +245,14 @@ func (h *Handler) SaveAs(w http.ResponseWriter, r *http.Request) {
} }
} }
err = h.Store.Space.IncrementContentCount(ctx, doc.SpaceID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Commit and return new document template // Commit and return new document template
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -430,6 +438,14 @@ func (h *Handler) Use(w http.ResponseWriter, r *http.Request) {
} }
} }
err = h.Store.Space.IncrementContentCount(ctx, d.SpaceID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeTemplateUse) h.Store.Audit.Record(ctx, audit.EventTypeTemplateUse)

View file

@ -405,7 +405,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
// remove all associated roles for this user // Remove user's permissions
_, err = h.Store.Permission.DeleteUserPermissions(ctx, userID) _, err = h.Store.Permission.DeleteUserPermissions(ctx, userID)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
@ -414,6 +414,15 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
// Remove all user groups memberships
err = h.Store.Group.RemoveUserGroups(ctx, userID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeUserDelete) h.Store.Audit.Record(ctx, audit.EventTypeUserDelete)

View file

@ -42,6 +42,7 @@ func AttachUserAccounts(ctx domain.RequestContext, s store.Store, orgID string,
u.Active = false u.Active = false
u.ViewUsers = false u.ViewUsers = false
u.Analytics = false u.Analytics = false
u.Theme = ""
for _, account := range u.Accounts { for _, account := range u.Accounts {
if account.OrgID == orgID { if account.OrgID == orgID {
@ -50,6 +51,7 @@ func AttachUserAccounts(ctx domain.RequestContext, s store.Store, orgID string,
u.Active = account.Active u.Active = account.Active
u.ViewUsers = account.Users u.ViewUsers = account.Users
u.Analytics = account.Analytics u.Analytics = account.Analytics
u.Theme = account.Theme
break break
} }
} }

View file

@ -13,6 +13,7 @@
package boot package boot
import ( import (
"os"
"time" "time"
"github.com/documize/community/core/database" "github.com/documize/community/core/database"
@ -57,28 +58,37 @@ func InitRuntime(r *env.Runtime, s *store.Store) bool {
storage.SetMySQLProvider(r, s) storage.SetMySQLProvider(r, s)
case "postgresql": case "postgresql":
storage.SetPostgreSQLProvider(r, s) storage.SetPostgreSQLProvider(r, s)
case "mssql": // case "mssql":
// storage.SetSQLServerProvider(r, s) // storage.SetSQLServerProvider(r, s)
default:
r.Log.Infof("Unsupported database type: %s", r.Flags.DBType)
r.Log.Info("Documize supports the following database types: mysql | mariadb | percona | postgresql")
os.Exit(1)
return false
} }
// Open connection to database // Open connection to database
db, err := sqlx.Open(r.StoreProvider.DriverName(), r.StoreProvider.MakeConnectionString()) //r.Flags.DBConn db, err := sqlx.Open(r.StoreProvider.DriverName(), r.StoreProvider.MakeConnectionString()) //r.Flags.DBConn
if err != nil { if err != nil {
r.Log.Error("unable to open database", err) r.Log.Error("Unable to open database", err)
os.Exit(1)
return false
} }
// Database handle // Set the database handle
r.Db = db r.Db = db
// Database connection defaults // Set connection defaults
r.Db.SetMaxIdleConns(30) r.Db.SetMaxIdleConns(30)
r.Db.SetMaxOpenConns(100) r.Db.SetMaxOpenConns(100)
r.Db.SetConnMaxLifetime(time.Second * 14400) r.Db.SetConnMaxLifetime(time.Second * 14400)
// Database good? // Ping verifies a connection to the database is still alive, establishing a connection if necessary.
err = r.Db.Ping() err = r.Db.Ping()
if err != nil { if err != nil {
r.Log.Error("unable to connect to database - "+r.StoreProvider.Example(), err) r.Log.Error("Unable to connect to database", err)
r.Log.Info(r.StoreProvider.Example())
os.Exit(1)
return false return false
} }

View file

@ -39,10 +39,10 @@ func main() {
// product details // product details
rt.Product = domain.Product{} rt.Product = domain.Product{}
rt.Product.Major = "1" rt.Product.Major = "2"
rt.Product.Minor = "76" rt.Product.Minor = "0"
rt.Product.Patch = "2" rt.Product.Patch = "0"
rt.Product.Revision = 181121115333 rt.Product.Revision = 190115203818
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)

View file

@ -27,6 +27,7 @@ import (
category "github.com/documize/community/domain/category" category "github.com/documize/community/domain/category"
document "github.com/documize/community/domain/document" document "github.com/documize/community/domain/document"
group "github.com/documize/community/domain/group" group "github.com/documize/community/domain/group"
label "github.com/documize/community/domain/label"
link "github.com/documize/community/domain/link" link "github.com/documize/community/domain/link"
meta "github.com/documize/community/domain/meta" meta "github.com/documize/community/domain/meta"
org "github.com/documize/community/domain/organization" org "github.com/documize/community/domain/organization"
@ -149,6 +150,11 @@ func SetMySQLProvider(r *env.Runtime, s *store.Store) {
userStore := user.Store{} userStore := user.Store{}
userStore.Runtime = r userStore.Runtime = r
s.User = userStore s.User = userStore
// Space Label
labelStore := label.Store{}
labelStore.Runtime = r
s.Label = labelStore
} }
// MySQLProvider supports MySQL 5.7.x and 8.0.x versions. // MySQLProvider supports MySQL 5.7.x and 8.0.x versions.

View file

@ -25,6 +25,7 @@ import (
category "github.com/documize/community/domain/category" category "github.com/documize/community/domain/category"
document "github.com/documize/community/domain/document" document "github.com/documize/community/domain/document"
group "github.com/documize/community/domain/group" group "github.com/documize/community/domain/group"
label "github.com/documize/community/domain/label"
link "github.com/documize/community/domain/link" link "github.com/documize/community/domain/link"
meta "github.com/documize/community/domain/meta" meta "github.com/documize/community/domain/meta"
org "github.com/documize/community/domain/organization" org "github.com/documize/community/domain/organization"
@ -147,6 +148,11 @@ func SetPostgreSQLProvider(r *env.Runtime, s *store.Store) {
userStore := user.Store{} userStore := user.Store{}
userStore.Runtime = r userStore.Runtime = r
s.User = userStore s.User = userStore
// Space Label
labelStore := label.Store{}
labelStore.Runtime = r
s.Label = labelStore
} }
// Type returns name of provider // Type returns name of provider

File diff suppressed because one or more lines are too long

View file

@ -4,3 +4,25 @@ public/tinymce
public/codemirror/** public/codemirror/**
public/codemirror/ public/codemirror/
public/codemirror public/codemirror
# unconventional js
/blueprints/*/files/
/vendor/
# compiled output
/dist/
/dist-prod/
/tmp/
# dependencies
/bower_components/
/node_modules/
# misc
/coverage/
!.*
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/package.json.ember-try

View file

@ -20,8 +20,10 @@ module.exports = {
// node files // node files
{ {
files: [ files: [
'testem.js', '.eslintrc.js',
'.template-lintrc.js',
'ember-cli-build.js', 'ember-cli-build.js',
'testem.js',
'config/**/*.js' 'config/**/*.js'
], ],
parserOptions: { parserOptions: {
@ -60,7 +62,6 @@ module.exports = {
"userLogin": true, "userLogin": true,
"Keycloak": true, "Keycloak": true,
"slug": true, "slug": true,
"interact": true, "iziToast": true
"velocity": true
} }
}; };

22
gui/.gitignore vendored
View file

@ -1,18 +1,24 @@
# See https://help.github.com/ignore-files/ for more about ignoring files. # See https://help.github.com/ignore-files/ for more about ignoring files.
# compiled output # compiled output
/dist /dist/
/dist-prod /tmp/
/tmp /dist-prod/
# dependencies # dependencies
/node_modules /bower_components/
/bower_components /node_modules/
# misc # misc
/.sass-cache /.sass-cache
/connect.lock /connect.lock
/coverage/* /coverage/
/libpeerconnection.log /libpeerconnection.log
npm-debug.log* /npm-debug.log*
testem.log /testem.log
/yarn-error.log
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/package.json.ember-try

19
gui/.template-lintrc.js Normal file
View file

@ -0,0 +1,19 @@
'use strict';
module.exports = {
extends: 'recommended',
rules: {
'attribute-indentation': false,
'block-indentation': false,
'img-alt-attributes': false,
'link-rel-noopener': false,
'no-inline-styles': false,
'no-invalid-interactive': false,
'no-nested-interactive': false,
'no-triple-curlies': false,
'style-concatenation': false,
'simple-unless': false,
}
};
// https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rules.md

View file

@ -25,5 +25,6 @@ install:
- yarn install --non-interactive - yarn install --non-interactive
script: script:
- yarn lint:js - npm run lint:hbs
- yarn test - npm run lint:js
- npm test

View file

@ -34,6 +34,12 @@ Make use of the many generators for code, try `ember help generate` for more det
* `ember test` * `ember test`
* `ember test --server` * `ember test --server`
### Linting
* `npm run lint:hbs`
* `npm run lint:js`
* `npm run lint:js -- --fix`
### Building ### Building
* `ember build` (development) * `ember build` (development)

View file

@ -141,15 +141,13 @@ export default Component.extend(ModalMixin, Notifier, {
}, },
onLDAPPreview() { onLDAPPreview() {
this.showWait();
let config = this.get('ldapConfig'); let config = this.get('ldapConfig');
config.serverPort = parseInt(this.get('ldapConfig.serverPort')); config.serverPort = parseInt(this.get('ldapConfig.serverPort'));
this.get('globalSvc').previewLDAP(config).then((preview) => { this.get('globalSvc').previewLDAP(config).then((preview) => {
this.set('ldapPreview', preview); this.set('ldapPreview', preview);
this.modalOpen("#ldap-preview-modal", {"show": true}); this.modalOpen("#ldap-preview-modal", {"show": true});
this.showDone(); this.notifySuccess('Saved');
}); });
}, },
@ -231,8 +229,6 @@ export default Component.extend(ModalMixin, Notifier, {
break; break;
} }
this.showWait();
let data = { authProvider: provider, authConfig: JSON.stringify(config) }; let data = { authProvider: provider, authConfig: JSON.stringify(config) };
this.get('onSave')(data).then(() => { this.get('onSave')(data).then(() => {
@ -274,7 +270,7 @@ export default Component.extend(ModalMixin, Notifier, {
}); });
} }
this.showDone(); this.notifySuccess('Saved');
}); });
} }
} }

View file

@ -60,7 +60,6 @@ export default Component.extend(Notifier, Modal, {
}, },
doBackup() { doBackup() {
this.showWait();
this.set('backupFilename', ''); this.set('backupFilename', '');
this.set('backupSuccess', false); this.set('backupSuccess', false);
this.set('backupFailed', false); this.set('backupFailed', false);
@ -69,13 +68,13 @@ export default Component.extend(Notifier, Modal, {
let spec = this.get('backupSpec'); let spec = this.get('backupSpec');
this.get('onBackup')(spec).then((filename) => { this.get('onBackup')(spec).then((filename) => {
this.showDone(); this.notifySuccess('Completed');
this.set('backupLabel', 'Start Backup'); this.set('backupLabel', 'Start Backup');
this.set('backupSuccess', true); this.set('backupSuccess', true);
this.set('backupFilename', filename); this.set('backupFilename', filename);
this.set('backupRunning', false); this.set('backupRunning', false);
}, ()=> { }, ()=> {
this.showDone(); this.notifyError('Failed');
this.set('backupLabel', 'Run Backup'); this.set('backupLabel', 'Run Backup');
this.set('backupFailed', true); this.set('backupFailed', true);
this.set('backupRunning', false); this.set('backupRunning', false);
@ -134,7 +133,6 @@ export default Component.extend(Notifier, Modal, {
} }
// start restore process // start restore process
this.showWait();
this.set('restoreButtonLabel', 'Please wait, restore running...'); this.set('restoreButtonLabel', 'Please wait, restore running...');
this.set('restoreSuccess', false); this.set('restoreSuccess', false);
this.set('restoreFailed', false); this.set('restoreFailed', false);
@ -147,12 +145,12 @@ export default Component.extend(Notifier, Modal, {
} }
this.get('onRestore')(spec, filedata).then(() => { this.get('onRestore')(spec, filedata).then(() => {
this.showDone(); this.notifySuccess('Completed');
this.set('backupLabel', 'Restore'); this.set('backupLabel', 'Restore');
this.set('restoreSuccess', true); this.set('restoreSuccess', true);
this.get('router').transitionTo('auth.logout'); this.get('router').transitionTo('auth.logout');
}, ()=> { }, ()=> {
this.showDone(); this.notifyError('Failed');
this.set('restorbackupLabel', 'Restore'); this.set('restorbackupLabel', 'Restore');
this.set('restoreFailed', true); this.set('restoreFailed', true);
}); });

View file

@ -13,10 +13,12 @@ import $ from 'jquery';
import { empty, and } from '@ember/object/computed'; import { empty, and } from '@ember/object/computed';
import { isEmpty } from '@ember/utils'; import { isEmpty } from '@ember/utils';
import { set } from '@ember/object'; import { set } from '@ember/object';
import { inject as service } from '@ember/service';
import Notifier from '../../mixins/notifier'; import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(Notifier, { export default Component.extend(Notifier, {
appMeta: service(),
maxTags: 3, maxTags: 3,
titleEmpty: empty('model.general.title'), titleEmpty: empty('model.general.title'),
messageEmpty: empty('model.general.message'), messageEmpty: empty('model.general.message'),
@ -30,6 +32,51 @@ export default Component.extend(Notifier, {
this.set('maxTags', this.get('model.general.maxTags')); this.set('maxTags', this.get('model.general.maxTags'));
}, },
didInsertElement() {
this._super(...arguments);
let self = this;
let url = this.get('appMeta.endpoint');
let orgId = this.get('appMeta.orgId');
let uploadUrl = `${url}/organization/${orgId}/logo`;
let dzone = new Dropzone("#upload-logo > div", {
headers: {
'Authorization': 'Bearer ' + self.get('session.authToken')
},
url: uploadUrl,
method: "post",
paramName: 'attachment',
clickable: true,
maxFilesize: 50,
parallelUploads: 1,
uploadMultiple: false,
addRemoveLinks: false,
autoProcessQueue: true,
createImageThumbnails: false,
init: function () {
this.on("success", function (/*file, response*/ ) {
});
this.on("queuecomplete", function () {
self.notifySuccess('Logo uploaded');
});
this.on("error", function (error, msg) {
self.notifyError(msg);
self.notifyError(error);
});
}
});
dzone.on("complete", function (file) {
dzone.removeFile(file);
});
this.set('drop', dzone);
},
actions: { actions: {
change() { change() {
const selectEl = this.$('#maxTags')[0]; const selectEl = this.$('#maxTags')[0];
@ -60,16 +107,23 @@ export default Component.extend(Notifier, {
} }
this.set('model.general.maxTags', this.get('maxTags')); this.set('model.general.maxTags', this.get('maxTags'));
this.model.general.set('allowAnonymousAccess', $("#allowAnonymousAccess").prop('checked'));
this.showWait(); this.get('onUpdate')().then(() => {
this.notifySuccess('Saved');
this.get('save')().then(() => {
this.showDone();
set(this, 'titleError', false); set(this, 'titleError', false);
set(this, 'messageError', false); set(this, 'messageError', false);
set(this, 'conversionEndpointError', false); set(this, 'conversionEndpointError', false);
}); });
},
onThemeChange(theme) {
this.get('appMeta').setTheme(theme);
this.set('model.general.theme', theme);
},
onDefaultLogo() {
this.get('onDefaultLogo')(this.get('appMeta.orgId'));
this.notifySuccess('Using default logo');
} }
} }
}); });

View file

@ -56,13 +56,12 @@ export default Component.extend(Notifier, {
this.set('jiraCreds.url', url.substring(0, url.length-1)); this.set('jiraCreds.url', url.substring(0, url.length-1));
} }
this.showWait();
this.get('orgSvc').saveOrgSetting(orgId, 'jira', this.get('jiraCreds')).then(() => { this.get('orgSvc').saveOrgSetting(orgId, 'jira', this.get('jiraCreds')).then(() => {
if (this.get('session.isGlobalAdmin')) { if (this.get('session.isGlobalAdmin')) {
this.get('orgSvc').saveGlobalSetting('SECTION-TRELLO', this.get('trelloCreds')); this.get('orgSvc').saveGlobalSetting('SECTION-TRELLO', this.get('trelloCreds'));
} }
this.showDone(); this.notifySuccess('Saved');
}); });
} }
} }

View file

@ -40,10 +40,8 @@ export default Component.extend(Notifier, Modals, {
actions: { actions: {
saveLicense() { saveLicense() {
this.showWait();
this.get('global').setLicense(this.get('license')).then(() => { this.get('global').setLicense(this.get('license')).then(() => {
this.showDone(); this.notifySuccess('Saved');
window.location.reload(); window.location.reload();
}); });
}, },
@ -57,7 +55,7 @@ export default Component.extend(Notifier, Modals, {
let comment = this.get('comment'); let comment = this.get('comment');
this.get('global').deactivate(comment).then(() => { this.get('global').deactivate(comment).then(() => {
this.showDone(); this.notifySuccess('Saved');
this.modalOpen("#deactivation-confirmation-modal", {"show": true}); this.modalOpen("#deactivation-confirmation-modal", {"show": true});
}); });
} }

View file

@ -10,16 +10,20 @@
// https://documize.com // https://documize.com
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend(Notifier, {
appMeta: service(), appMeta: service(),
buttonLabel: 'Rebuild Search Index', buttonLabel: 'Rebuild',
actions: { actions: {
reindex() { reindex() {
this.set('buttonLabel', 'Rebuilding search index...') this.set('buttonLabel', 'Running...');
this.get('reindex')(); this.notifyInfo("Starting search re-index process");
this.get('reindex')(() => {
this.notifySuccess("Search re-indexing complete");
});
} }
} }
}); });

View file

@ -48,13 +48,18 @@ export default Component.extend(Notifier, {
}, },
); );
this.showWait();
this.set('buttonText', 'Please wait...'); this.set('buttonText', 'Please wait...');
this.notifyInfo('Sending test email to you');
this.get('saveSMTP')().then((result) => { this.get('saveSMTP')().then((result) => {
this.showDone();
this.set('buttonText', 'Save & Test'); this.set('buttonText', 'Save & Test');
this.set('testSMTP', result); this.set('testSMTP', result);
if (result.success) {
this.notifySuccess(result.message);
} else {
this.notifyError(result.message);
}
}); });
} }
} }

View file

@ -33,13 +33,11 @@ export default Component.extend(Notifier, Modals, {
init() { init() {
this._super(...arguments); this._super(...arguments);
this.loadData(); this.loadData();
}, },
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
this.deleteSpace = { this.deleteSpace = {
id: '', id: '',
name: '' name: ''
@ -55,6 +53,7 @@ export default Component.extend(Notifier, Modals, {
actions: { actions: {
onShow(id) { onShow(id) {
this.set('deleteSpace.id', id); this.set('deleteSpace.id', id);
this.modalOpen("#space-delete-modal", {"show": true}, '#delete-space-name');
}, },
onDelete() { onDelete() {
@ -76,29 +75,28 @@ export default Component.extend(Notifier, Modals, {
this.set('deleteSpace.id', ''); this.set('deleteSpace.id', '');
this.set('deleteSpace.name', ''); this.set('deleteSpace.name', '');
this.loadData(); this.loadData();
this.notifySuccess('Deleted');
}); });
}, },
onExport() { onExport() {
this.showWait();
let spec = { let spec = {
spaceId: '', spaceId: '',
data: _.pluck(this.get('folders'), 'id'), data: _.pluck(this.get('folders'), 'id'),
filterType: 'space', filterType: 'space',
}; };
this.notifyInfo('Export running...');
this.get('documentSvc').export(spec).then((htmlExport) => { this.get('documentSvc').export(spec).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, 'documize.html'); this.get('browserSvc').downloadFile(htmlExport, 'documize.html');
this.showDone(); this.notifySuccess('Export completed');
}); });
}, },
onOwner(spaceId) { onOwner(spaceId) {
this.showWait();
this.get('spaceSvc').grantOwnerPermission(spaceId).then(() => { /* jshint ignore:line */ this.get('spaceSvc').grantOwnerPermission(spaceId).then(() => { /* jshint ignore:line */
this.showDone(); this.notifySuccess('Added as owner');
}); });
} }
} }

View file

@ -0,0 +1,94 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import $ from 'jquery';
import Modals from '../../mixins/modal';
import Component from '@ember/component';
export default Component.extend(Modals, {
labelName: '',
labelColor: '',
editLabel: null,
deleetLabel: null,
showDeleteDialog: false,
actions: {
onShowAddModal() {
this.set('labelName', '');
this.set('labelColor', '');
this.modalOpen("#add-label-modal", {"show": true}, '#add-label-name');
},
onShowDeleteModal(label) {
this.set('deleteLabel', label);
this.set('showDeleteDialog', !this.get('showDeleteDialog'));
},
onShowUpdateModal(label) {
this.set('editLabel', label);
this.set('labelName', label.get('name'));
this.set('labelColor', label.get('color'));
this.modalOpen("#edit-label-modal", {"show": true}, '#edit-label-name');
},
onSetColor(color) {
this.set('labelColor', color);
},
onAdd() {
let label = {
name: this.get('labelName').trim(),
color: this.get('labelColor').trim(),
}
if (is.empty(label.name)) {
$('#add-label-name').addClass('is-invalid').focus();
return;
}
$('#add-label-name').removeClass('is-invalid');
this.modalClose('#add-label-modal');
this.get('onAdd')(label);
},
onUpdate() {
let name = this.get('labelName').trim();
let color = this.get('labelColor').trim();
let label = this.get('editLabel');
if (is.empty(name)) {
$('#edit-label-name').addClass('is-invalid').focus();
return;
}
$('#edit-label-name').removeClass('is-invalid');
this.modalClose('#edit-label-modal');
label.set('name', name);
label.set('color', color);
this.get('onUpdate')(label);
this.set('editLabel', null);
},
onDelete() {
let label = this.get('deleteLabel');
this.set('showDeleteDialog', false);
this.get('onDelete')(label.get('id'));
this.set('deleteLabel', null);
return true;
}
}
});

View file

@ -12,9 +12,10 @@
import $ from 'jquery'; import $ from 'jquery';
import AuthProvider from '../../mixins/auth'; import AuthProvider from '../../mixins/auth';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(AuthProvider, ModalMixin, { export default Component.extend(AuthProvider, ModalMixin, Notifier, {
bulkUsers: '', bulkUsers: '',
newUser: null, newUser: null,
@ -51,6 +52,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
this.get('onAddUser')(user).then(() => { this.get('onAddUser')(user).then(() => {
this.set('newUser', { firstname: '', lastname: '', email: '', active: true }); this.set('newUser', { firstname: '', lastname: '', email: '', active: true });
this.notifySuccess('Added user');
}); });
this.modalClose("#add-user-modal"); this.modalClose("#add-user-modal");
@ -65,6 +67,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
this.get('onAddUsers')(this.get('bulkUsers')).then(() => { this.get('onAddUsers')(this.get('bulkUsers')).then(() => {
this.set('bulkUsers', ''); this.set('bulkUsers', '');
this.notifySuccess('Added users');
}); });
this.modalClose("#add-user-modal"); this.modalClose("#add-user-modal");

View file

@ -24,7 +24,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
searchText: '', searchText: '',
users: null, users: null,
members: null, members: null,
userLimit: 100, userLimit: 25,
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);

View file

@ -10,20 +10,22 @@
// https://documize.com // https://documize.com
import $ from 'jquery'; import $ from 'jquery';
import { observer } from '@ember/object';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { schedule, debounce } from '@ember/runloop'; import { schedule, debounce } from '@ember/runloop';
import TooltipMixin from '../../mixins/tooltip';
import AuthProvider from '../../mixins/auth'; import AuthProvider from '../../mixins/auth';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(AuthProvider, ModalMixin, TooltipMixin, { export default Component.extend(AuthProvider, ModalMixin, Notifier, {
groupSvc: service('group'), groupSvc: service('group'),
editUser: null, editUser: null,
deleteUser: null, deleteUser: null,
filter: '', filter: '',
hasSelectedUsers: false, hasSelectedUsers: false,
showDeleteDialog: false, showDeleteDialog: false,
showPermExplain: false,
init() { init() {
this._super(...arguments); this._super(...arguments);
@ -46,24 +48,27 @@ export default Component.extend(AuthProvider, ModalMixin, TooltipMixin, {
}); });
this.set('users', users); this.set('users', users);
this.renderTooltips();
}, },
willDestroyElement() { onKeywordChange: observer('filter', function() {
this._super(...arguments);
this.removeTooltips();
},
onKeywordChange: function () {
debounce(this, this.filterUsers, 350); debounce(this, this.filterUsers, 350);
}.observes('filter'), }),
filterUsers() { filterUsers() {
this.get('onFilter')(this.get('filter')); this.get('onFilter')(this.get('filter'));
}, },
actions: { actions: {
togglePerms() {
this.set('showPermExplain', !this.get('showPermExplain'));
if (this.showPermExplain) {
this.$(".perms").show();
} else {
this.$(".perms").hide();
}
},
toggleSelect(user) { toggleSelect(user) {
user.set('selected', !user.get('selected')); user.set('selected', !user.get('selected'));
@ -178,9 +183,15 @@ export default Component.extend(AuthProvider, ModalMixin, TooltipMixin, {
let cb = this.get('onDelete'); let cb = this.get('onDelete');
cb(this.get('deleteUser.id')); cb(this.get('deleteUser.id'));
this.notifySuccess("Deleted user");
return true; return true;
}, },
onShowDeleteBulk() {
this.modalOpen("#admin-user-delete-modal", {"show": true});
},
onBulkDelete() { onBulkDelete() {
let su = this.get('selectedUsers'); let su = this.get('selectedUsers');
@ -192,6 +203,8 @@ export default Component.extend(AuthProvider, ModalMixin, TooltipMixin, {
this.set('selectedUsers', []); this.set('selectedUsers', []);
this.set('hasSelectedUsers', false); this.set('hasSelectedUsers', false);
this.notifySuccess("Deleted selected users");
this.modalClose('#admin-user-delete-modal'); this.modalClose('#admin-user-delete-modal');
}, },

View file

@ -12,13 +12,12 @@
import $ from 'jquery'; import $ from 'jquery';
import { empty } from '@ember/object/computed'; import { empty } from '@ember/object/computed';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { computed } from '@ember/object'; import { computed, observer } from '@ember/object';
import Tooltips from '../../mixins/tooltip';
import Notifier from '../../mixins/notifier'; import Notifier from '../../mixins/notifier';
import Modals from '../../mixins/modal'; import Modals from '../../mixins/modal';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(Tooltips, Notifier, Modals, { export default Component.extend(Notifier, Modals, {
documentService: service('document'), documentService: service('document'),
sectionService: service('section'), sectionService: service('section'),
store: service(), store: service(),
@ -34,7 +33,7 @@ export default Component.extend(Tooltips, Notifier, Modals, {
return this.get('blocks.length') > 0; return this.get('blocks.length') > 0;
}), }),
onModalToggle: function () { onModalToggle: observer('show', function() {
let modalId = this.get('modalId'); let modalId = this.get('modalId');
if (this.get('show')) { if (this.get('show')) {
@ -49,7 +48,7 @@ export default Component.extend(Tooltips, Notifier, Modals, {
$(modalId).modal('hide'); $(modalId).modal('hide');
$(modalId).modal('dispose'); $(modalId).modal('dispose');
} }
}.observes('show'), }),
addSection(model) { addSection(model) {
this.modalClose(this.get('modalId')); this.modalClose(this.get('modalId'));

View file

@ -10,14 +10,13 @@
// https://documize.com // https://documize.com
import { debounce } from '@ember/runloop'; import { debounce } from '@ember/runloop';
import { computed, set } from '@ember/object'; import { computed, set, observer } from '@ember/object';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import stringUtil from '../../utils/string'; import stringUtil from '../../utils/string';
import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, { export default Component.extend(ModalMixin, {
link: service(), link: service(),
linkName: '', linkName: '',
selection: null, selection: null,
@ -36,7 +35,7 @@ export default Component.extend(ModalMixin, TooltipMixin, {
}), }),
modalId: computed('page', function() { return '#content-linker-modal-' + this.get('page.id'); }), modalId: computed('page', function() { return '#content-linker-modal-' + this.get('page.id'); }),
showModal: false, showModal: false,
onToggle: function() { onToggle: observer('showModal', function() {
let modalId = this.get('modalId'); let modalId = this.get('modalId');
if (!this.get('showModal')) { if (!this.get('showModal')) {
@ -56,7 +55,7 @@ export default Component.extend(ModalMixin, TooltipMixin, {
}); });
this.modalOpen(modalId, {show: true}); this.modalOpen(modalId, {show: true});
}.observes('showModal'), }),
init() { init() {
this._super(...arguments); this._super(...arguments);
@ -71,18 +70,16 @@ export default Component.extend(ModalMixin, TooltipMixin, {
this._super(...arguments); this._super(...arguments);
this.$('#content-linker-networklocation').removeClass('is-invalid'); this.$('#content-linker-networklocation').removeClass('is-invalid');
this.renderTooltips();
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.removeTooltips();
this.modalClose(this.get('modalId')); this.modalClose(this.get('modalId'));
}, },
onKeywordChange: function() { onKeywordChange: observer('keywords', function() {
debounce(this, this.fetch, 750); debounce(this, this.fetch, 750);
}.observes('keywords'), }),
fetch() { fetch() {
let keywords = this.get('keywords'); let keywords = this.get('keywords');

View file

@ -9,176 +9,30 @@
// //
// https://documize.com // https://documize.com
import $ from 'jquery';
import { A } from '@ember/array'; import { A } from '@ember/array';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { notEmpty } from '@ember/object/computed'; import { notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Modals from '../../mixins/modal'; import Modals from '../../mixins/modal';
import Tooltips from '../../mixins/tooltip';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(Modals, Tooltips, { export default Component.extend(Modals, {
documentService: service('document'), documentService: service('document'),
sessionService: service('session'), sessionService: service('session'),
categoryService: service('category'), categoryService: service('category'),
router: service(), router: service(),
selectedCategories: A([]),
contributorMsg: '', tagz: A([]),
approverMsg: '',
userChanges: notEmpty('contributorMsg'), userChanges: notEmpty('contributorMsg'),
isApprover: computed('permissions', function() { unassigned: computed('selectedCategories', 'tagz', function() {
return this.get('permissions.documentApprove'); return this.get('selectedCategories').length === 0 && this.get('tagz').length === 0;
}),
isSpaceAdmin: computed('permissions', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
changeControlMsg: computed('document.protection', function() {
let p = this.get('document.protection');
let constants = this.get('constants');
let msg = '';
switch (p) {
case constants.ProtectionType.None:
msg = constants.ProtectionType.NoneLabel;
break;
case constants.ProtectionType.Lock:
msg = constants.ProtectionType.LockLabel;
break;
case constants.ProtectionType.Review:
msg = constants.ProtectionType.ReviewLabel;
break;
}
return msg;
}),
approvalMsg: computed('document.{protection,approval}', function() {
let p = this.get('document.protection');
let a = this.get('document.approval');
let constants = this.get('constants');
let msg = '';
if (p === constants.ProtectionType.Review) {
switch (a) {
case constants.ApprovalType.Anybody:
msg = constants.ApprovalType.AnybodyLabel;
break;
case constants.ApprovalType.Majority:
msg = constants.ApprovalType.MajorityLabel;
break;
case constants.ApprovalType.Unanimous:
msg = constants.ApprovalType.UnanimousLabel;
break;
}
}
return msg;
}), }),
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
this.workflowStatus();
this.popovers();
this.load(); this.load();
}, },
didInsertElement() {
this._super(...arguments);
this.popovers();
this.renderTooltips();
},
willDestroyElement() {
this._super(...arguments);
$('#document-lifecycle-popover').popover('dispose');
$('#document-protection-popover').popover('dispose');
this.removeTooltips();
},
popovers() {
let constants = this.get('constants');
if (this.get('permissions.documentLifecycle')) {
$('#document-lifecycle-popover').addClass('cursor-pointer');
} else {
$('#document-lifecycle-popover').popover('dispose');
$('#document-lifecycle-popover').removeClass('cursor-pointer');
$('#document-lifecycle-popover').popover({
html: true,
title: 'Lifecycle',
content: "<p>Draft &mdash; restricted visiblity and not searchable</p><p>Live &mdash; document visible to all</p><p>Archived &mdash; not visible or searchable</p>",
placement: 'top',
trigger: 'hover click'
});
}
if (this.get('permissions.documentApprove')) {
$('#document-protection-popover').addClass('cursor-pointer');
} else {
$('#document-protection-popover').popover('dispose');
$('#document-protection-popover').removeClass('cursor-pointer');
let ccMsg = `<p>${this.changeControlMsg}</p>`;
if (this.get('document.protection') === constants.ProtectionType.Review) {
ccMsg += '<ul>'
ccMsg += `<li>${this.approvalMsg}</li>`;
if (this.get('userChanges')) ccMsg += `<li>Your contributions: ${this.contributorMsg}</li>`;
if (this.get('isApprover') && this.get('approverMsg.length') > 0) ccMsg += `<li>${this.approverMsg}</li>`;
ccMsg += '</ul>'
}
$('#document-protection-popover').popover({
html: true,
title: 'Change Control',
content: ccMsg,
placement: 'top',
trigger: 'hover click'
});
}
},
workflowStatus() {
let pages = this.get('pages');
let contributorMsg = '';
let userPendingCount = 0;
let userReviewCount = 0;
let userRejectedCount = 0;
let approverMsg = '';
let approverPendingCount = 0;
let approverReviewCount = 0;
let approverRejectedCount = 0;
pages.forEach((item) => {
if (item.get('userHasChangePending')) userPendingCount+=1;
if (item.get('userHasChangeAwaitingReview')) userReviewCount+=1;
if (item.get('userHasChangeRejected')) userRejectedCount+=1;
if (item.get('changePending')) approverPendingCount+=1;
if (item.get('changeAwaitingReview')) approverReviewCount+=1;
if (item.get('changeRejected')) approverRejectedCount+=1;
});
if (userPendingCount > 0 || userReviewCount > 0 || userRejectedCount > 0) {
let label = userPendingCount === 1 ? 'change' : 'changes';
contributorMsg = `${userPendingCount} ${label} progressing, ${userReviewCount} awaiting review, ${userRejectedCount} rejected`;
}
this.set('contributorMsg', contributorMsg);
if (approverPendingCount > 0 || approverReviewCount > 0 || approverRejectedCount > 0) {
let label = approverPendingCount === 1 ? 'change' : 'changes';
approverMsg = `${approverPendingCount} ${label} progressing, ${approverReviewCount} awaiting review, ${approverRejectedCount} rejected`;
}
this.set('approverMsg', approverMsg);
this.set('selectedVersion', this.get('versions').findBy('documentId', this.get('document.id')));
this.popovers();
},
load() { load() {
this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => { this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => {
this.set('selectedCategories', selected); this.set('selectedCategories', selected);
@ -198,24 +52,10 @@ export default Component.extend(Modals, Tooltips, {
}, },
actions: { actions: {
onSelectVersion(version) {
let space = this.get('folder');
this.get('router').transitionTo('document',
space.get('id'), space.get('slug'),
version.documentId, this.get('document.slug'));
},
onEditLifecycle() {
},
onEditProtection() {
},
onEditCategory() { onEditCategory() {
if (!this.get('permissions.spaceManage')) return; if (!this.get('permissions.documentEdit')) return;
this.get('router').transitionTo('document.settings', {queryParams: {tab: 'meta'}}); this.get('router').transitionTo('document.settings', {queryParams: {tab: 'category'}});
} }
} }
}); });

View file

@ -11,9 +11,8 @@
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component'; import Component from '@ember/component';
import TooltipMixin from '../../mixins/tooltip';
export default Component.extend(TooltipMixin, { export default Component.extend({
documentService: service('document'), documentService: service('document'),
sectionService: service('section'), sectionService: service('section'),
editMode: false, editMode: false,

View file

@ -11,13 +11,13 @@
import $ from 'jquery'; import $ from 'jquery';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import AuthMixin from '../../mixins/auth'; import AuthMixin from '../../mixins/auth';
import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Notifier from '../../mixins/notifier'; import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, { export default Component.extend(ModalMixin, AuthMixin, Notifier, {
store: service(), store: service(),
spaceSvc: service('folder'), spaceSvc: service('folder'),
session: service(), session: service(),
@ -25,6 +25,29 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
pinned: service(), pinned: service(),
browserSvc: service('browser'), browserSvc: service('browser'),
documentSvc: service('document'), documentSvc: service('document'),
showRevisions: computed('permissions', 'document.protection', function() {
if (!this.get('session.authenticated')) return false;
if (!this.get('session.viewUsers')) return false;
if (this.get('document.protection') === this.get('constants').ProtectionType.None) return true;
if (this.get('document.protection') === this.get('constants').ProtectionType.Review && this.get('permissions.documentApprove')) return true;
return false;
}),
showActivity: computed('permissions', function() {
if (this.get('appMeta.edition') !== this.get('constants').Product.EnterpriseEdition) return false;
if (!this.get('session.authenticated')) return false;
if (!this.get('session.viewUsers')) return false;
if (this.get('permissions.spaceView')) return true;
return false;
}),
hasToolbar: computed('permissions', 'showRevisions', 'showActivity', function() {
if (this.get('showRevisions') || this.get('showActivity')) return true;
if (this.get('permissions.documentAdd') || this.get('permissions.documentDelete')) return true;
if (this.get('appMeta.edition') === this.get('constants').Product.EnterpriseEdition &&
this.get('permissions.documentEdit')) return true;
}),
init() { init() {
this._super(...arguments); this._super(...arguments);
@ -49,24 +72,25 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
this.set('pinState.pinId', pinId); this.set('pinState.pinId', pinId);
this.set('pinState.isPinned', pinId !== ''); this.set('pinState.isPinned', pinId !== '');
this.set('pinState.newName', doc.get('name')); this.set('pinState.newName', doc.get('name'));
this.renderTooltips();
}); });
this.set('saveTemplate.name', this.get('document.name')); this.set('saveTemplate.name', this.get('document.name'));
this.set('saveTemplate.description', this.get('document.excerpt')); this.set('saveTemplate.description', this.get('document.excerpt'));
}, },
didInsertElement() {
this._super(...arguments);
this.modalInputFocus('#document-template-modal', '#new-template-name');
},
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.removeTooltips();
}, },
actions: { actions: {
onShowTemplateModal() {
this.modalOpen("#document-template-modal", {show:true}, "#new-template-name");
},
onShowDeleteModal() {
this.modalOpen("#document-delete-modal", {show:true});
},
onDocumentDelete() { onDocumentDelete() {
this.modalClose('#document-delete-modal'); this.modalClose('#document-delete-modal');
@ -80,11 +104,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
onUnpin() { onUnpin() {
this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => { this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => {
$('#document-pin-button').tooltip('dispose');
this.set('pinState.isPinned', false); this.set('pinState.isPinned', false);
this.set('pinState.pinId', ''); this.set('pinState.pinId', '');
this.eventBus.publish('pinChange'); this.eventBus.publish('pinChange');
this.renderTooltips();
}); });
}, },
@ -96,11 +118,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
}; };
this.get('pinned').pinItem(pin).then((pin) => { this.get('pinned').pinItem(pin).then((pin) => {
$('#document-pin-button').tooltip('dispose');
this.set('pinState.isPinned', true); this.set('pinState.isPinned', true);
this.set('pinState.pinId', pin.get('id')); this.set('pinState.pinId', pin.get('id'));
this.eventBus.publish('pinChange'); this.eventBus.publish('pinChange');
this.renderTooltips();
}); });
return true; return true;
@ -135,10 +155,8 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
}, },
onExport() { onExport() {
this.showWait();
let spec = { let spec = {
spaceId: this.get('document.folderId'), spaceId: this.get('document.spaceId'),
data: [], data: [],
filterType: 'document', filterType: 'document',
}; };
@ -147,7 +165,7 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
this.get('documentSvc').export(spec).then((htmlExport) => { this.get('documentSvc').export(spec).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, this.get('document.slug') + '.html'); this.get('browserSvc').downloadFile(htmlExport, this.get('document.slug') + '.html');
this.showDone(); this.notifySuccess('Exported');
}); });
} }
} }

View file

@ -10,15 +10,14 @@
// https://documize.com // https://documize.com
import $ from 'jquery'; import $ from 'jquery';
import { computed } from '@ember/object'; import { computed, observer } from '@ember/object';
import { debounce } from '@ember/runloop'; import { debounce } from '@ember/runloop';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Tooltips from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import tocUtil from '../../utils/toc'; import tocUtil from '../../utils/toc';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, Tooltips, { export default Component.extend(ModalMixin, {
documentService: service('document'), documentService: service('document'),
searchService: service('search'), searchService: service('search'),
router: service(), router: service(),
@ -29,9 +28,9 @@ export default Component.extend(ModalMixin, Tooltips, {
canDelete: false, canDelete: false,
canMove: false, canMove: false,
docSearchFilter: '', docSearchFilter: '',
onKeywordChange: function () { onKeywordChange: observer('docSearchFilter', function() {
debounce(this, this.searchDocs, 750); debounce(this, this.searchDocs, 750);
}.observes('docSearchFilter'), }),
emptySearch: computed('docSearchResults', function() { emptySearch: computed('docSearchResults', function() {
return this.get('docSearchResults.length') === 0; return this.get('docSearchResults.length') === 0;
}), }),
@ -78,14 +77,6 @@ export default Component.extend(ModalMixin, Tooltips, {
this.setState(this.get('page.id')); this.setState(this.get('page.id'));
}, },
didInsertElement() {
this._super(...arguments);
if (this.get('session.authenticated')) {
this.renderTooltips();
}
},
searchDocs() { searchDocs() {
let payload = { keywords: this.get('docSearchFilter').trim(), doc: true }; let payload = { keywords: this.get('docSearchFilter').trim(), doc: true };
if (payload.keywords.length == 0) return; if (payload.keywords.length == 0) return;

View file

@ -125,8 +125,6 @@ export default Component.extend(Notifier, {
actions: { actions: {
onSave() { onSave() {
this.showWait();
let docId = this.get('document.id'); let docId = this.get('document.id');
let folderId = this.get('space.id'); let folderId = this.get('space.id');
let link = this.get('categories').filterBy('selected', true); let link = this.get('categories').filterBy('selected', true);
@ -158,7 +156,6 @@ export default Component.extend(Notifier, {
this.get('categoryService').setCategoryMembership(toUnlink, 'unlink').then(() => { this.get('categoryService').setCategoryMembership(toUnlink, 'unlink').then(() => {
this.get('categoryService').setCategoryMembership(toLink, 'link').then(() => { this.get('categoryService').setCategoryMembership(toLink, 'link').then(() => {
this.showDone();
}); });
}); });

View file

@ -32,7 +32,7 @@ export default Component.extend(Notifier, {
if (this.get('hasNameError')) return; if (this.get('hasNameError')) return;
if (!this.get('permissions.documentEdit')) return; if (!this.get('permissions.documentEdit')) return;
this.set('document.name', this.get('docName')); this.set('document.name', this.get('docName').trim());
this.set('document.excerpt', this.get('docExcerpt').trim()); this.set('document.excerpt', this.get('docExcerpt').trim());
let cb = this.get('onSaveDocument'); let cb = this.get('onSaveDocument');

View file

@ -0,0 +1,189 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import $ from 'jquery';
import { inject as service } from '@ember/service';
import { A } from '@ember/array';
import { computed } from '@ember/object';
import { schedule } from '@ember/runloop';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component';
export default Component.extend(Notifier, {
appMeta: service(),
documentSvc: service('document'),
categoryService: service('category'),
tagz: A([]),
categories: A([]),
newCategory: '',
showCategoryModal: false,
hasCategories: computed('categories', function() {
return this.get('categories').length > 0;
}),
canSelectCategory: computed('categories', function() {
return (this.get('categories').length > 0 && this.get('permissions.documentEdit'));
}),
canAddCategory: computed('categories', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
didReceiveAttrs() {
this._super(...arguments);
this.load();
},
didInsertElement() {
this._super(...arguments);
schedule('afterRender', () => {
$("#add-tag-field-1").focus();
$(".tag-input").off("keydown").on("keydown", function(e) {
if (e.shiftKey && e.which === 9) {
return true;
}
if (e.shiftKey) {
return false;
}
if (e.which === 9 ||
e.which === 13 ||
e.which === 16 ||
e.which === 37 ||
e.which === 38 ||
e.which === 39 ||
e.which === 40 ||
e.which === 45 ||
e.which === 189 ||
e.which === 8 ||
e.which === 127 ||
(e.which >= 65 && e.which <= 90) ||
(e.which >= 97 && e.which <= 122) ||
(e.which >= 48 && e.which <= 57)) {
return true;
}
return false;
});
});
},
willDestroyElement() {
this._super(...arguments);
$(".tag-input").off("keydown");
},
load() {
this.get('categoryService').getUserVisible(this.get('space.id')).then((categories) => {
let cats = A(categories);
this.set('categories', cats);
this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => {
this.set('selectedCategories', selected);
selected.forEach((s) => {
let cat = cats.findBy('id', s.id);
if (is.not.undefined(cat)) {
cat.set('selected', true);
this.set('categories', cats);
}
});
});
});
let counter = 1;
let tagz = A([]);
let maxTags = this.get('appMeta.maxTags');
if (!_.isUndefined(this.get('document.tags')) && this.get('document.tags').length > 1) {
let tags = this.get('document.tags').split('#');
_.each(tags, (tag) => {
tag = tag.trim();
if (tag.length > 0 && counter <= maxTags) {
tagz.pushObject({number: counter, value: tag});
counter++;
}
});
}
for (let index = counter; index <= maxTags; index++) {
tagz.pushObject({number: index, value: ''});
}
this.set('tagz', tagz);
},
actions: {
onSave() {
let docId = this.get('document.id');
let folderId = this.get('space.id');
let link = this.get('categories').filterBy('selected', true);
let unlink = this.get('categories').filterBy('selected', false);
let toLink = [];
let toUnlink = [];
// prepare links associated with document
link.forEach((l) => {
let t = {
spaceId: folderId,
documentId: docId,
categoryId: l.get('id')
};
toLink.push(t);
});
// prepare links no longer associated with document
unlink.forEach((l) => {
let t = {
spaceId: folderId,
documentId: docId,
categoryId: l.get('id')
};
toUnlink.pushObject(t);
});
this.get('categoryService').setCategoryMembership(toUnlink, 'unlink').then(() => {
this.get('categoryService').setCategoryMembership(toLink, 'link').then(() => {
});
});
let tagz = this.get('tagz');
let tagzToSave = [];
_.each(tagz, (t) => {
let tag = t.value.toLowerCase().trim();
if (tag.length> 0) {
if (!_.contains(tagzToSave, tag) && is.not.startWith(tag, '-')) {
tagzToSave.push(tag);
this.$('#add-tag-field-' + t.number).removeClass('is-invalid');
} else {
this.$('#add-tag-field-' + t.number).addClass('is-invalid');
}
}
});
let save = "#";
_.each(tagzToSave, (t) => {
save += t;
save += '#';
});
let doc = this.get('document');
doc.set('tags', save);
this.get('onSaveDocument')(doc);
}
}
});

View file

@ -17,18 +17,17 @@ import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(Modals, Notifier, { export default Component.extend(Modals, Notifier, {
classNames: ["section"],
documentService: service('document'), documentService: service('document'),
browserSvc: service('browser'),
appMeta: service(), appMeta: service(),
session: service(),
hasAttachments: notEmpty('files'), hasAttachments: notEmpty('files'),
canEdit: computed('permissions.documentEdit', 'document.protection', function() { canEdit: computed('permissions.documentEdit', 'document.protection', function() {
return this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit'); return this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit');
}), }),
showDialog: false, showDialog: false,
downloadQuery: '',
init() {
this._super(...arguments);
this.deleteAttachment = { id: '', name: '' };
},
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
@ -47,9 +46,9 @@ export default Component.extend(Modals, Notifier, {
let url = this.get('appMeta.endpoint'); let url = this.get('appMeta.endpoint');
let uploadUrl = `${url}/documents/${documentId}/attachments`; let uploadUrl = `${url}/documents/${documentId}/attachments`;
let dzone = new Dropzone("#upload-document-files", { let dzone = new Dropzone("#upload-document-files > div", {
headers: { headers: {
'Authorization': 'Bearer ' + self.get('session.session.content.authenticated.token') 'Authorization': 'Bearer ' + self.get('session.authToken')
}, },
url: uploadUrl, url: uploadUrl,
method: "post", method: "post",
@ -66,17 +65,16 @@ export default Component.extend(Modals, Notifier, {
}); });
this.on("queuecomplete", function () { this.on("queuecomplete", function () {
self.showDone(); self.notifySuccess('Uploaded file');
self.getAttachments(); self.getAttachments();
}); });
this.on("addedfile", function ( /*file*/ ) { this.on("addedfile", function ( /*file*/ ) {
self.showWait();
}); });
this.on("error", function (error, msg) { // // eslint-disable-line no-unused-vars this.on("error", function (error, msg) {
self.showNotification(msg); self.notifyError(msg);
console.log(msg); // eslint-disable-line no-console self.notifyError(error);
}); });
} }
}); });
@ -86,6 +84,15 @@ export default Component.extend(Modals, Notifier, {
}); });
this.set('drop', dzone); this.set('drop', dzone);
// For authenticated users we send server auth token.
let qry = '';
if (this.get('session.hasSecureToken')) {
qry = '?secure=' + this.get('session.secureToken');
} else if (this.get('session.authenticated')) {
qry = '?token=' + this.get('session.authToken');
}
this.set('downloadQuery', qry);
}, },
getAttachments() { getAttachments() {
@ -95,26 +102,20 @@ export default Component.extend(Modals, Notifier, {
}, },
actions: { actions: {
onShowDialog(id, name) { onDelete(attachment) {
this.set('deleteAttachment', { id: id, name: name }); this.get('documentService').deleteAttachment(this.get('document.id'), attachment.id).then(() => {
this.notifySuccess('File deleted');
this.set('showDialog', true); this.getAttachments();
});
}, },
onDelete() { onExport() {
this.set('showDialog', false); this.get('documentSvc').export({}).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, this.get('space.slug') + '.html');
let attachment = this.get('deleteAttachment'); this.notifySuccess('Exported');
this.get('documentService').deleteAttachment(this.get('document.id'), attachment.id).then(() => {
this.getAttachments();
this.set('deleteAttachment', {
id: "",
name: ""
});
}); });
return true; this.modalClose("#space-export-modal");
} }
} }
}); });

View file

@ -0,0 +1,41 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import { computed } from '@ember/object';
import { notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Modals from '../../mixins/modal';
import Component from '@ember/component';
export default Component.extend(Modals, {
appMeta: service(),
documentService: service('document'),
sessionService: service('session'),
router: service(),
userChanges: notEmpty('contributorMsg'),
unassigned: computed('selectedCategories', 'tagz', function() {
return this.get('selectedCategories').length === 0 && this.get('tagz').length === 0;
}),
actions: {
onEditStatus() {
if (!this.get('permissions.documentEdit')) return;
this.get('router').transitionTo('document.settings', {queryParams: {tab: 'general'}});
},
onSelectVersion(version) {
let space = this.get('space');
this.get('router').transitionTo('document', space.get('id'), space.get('slug'), version.documentId, this.get('document.slug'));
}
}
});

View file

@ -13,10 +13,10 @@ import { computed } from '@ember/object';
import { schedule } from '@ember/runloop'; import { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import tocUtil from '../../utils/toc'; import tocUtil from '../../utils/toc';
import TooltipMixin from '../../mixins/tooltip';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(TooltipMixin, { export default Component.extend({
classNames: ["section"],
documentService: service('document'), documentService: service('document'),
emptyState: computed('pages', function () { emptyState: computed('pages', function () {
return this.get('pages.length') === 0; return this.get('pages.length') === 0;
@ -54,13 +54,11 @@ export default Component.extend(TooltipMixin, {
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
this.eventBus.subscribe('documentPageAdded', this, 'onDocumentPageAdded'); this.eventBus.subscribe('documentPageAdded', this, 'onDocumentPageAdded');
this.renderTooltips();
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.eventBus.unsubscribe('documentPageAdded'); this.eventBus.unsubscribe('documentPageAdded');
this.removeTooltips();
}, },
onDocumentPageAdded(pageId) { onDocumentPageAdded(pageId) {

View file

@ -13,16 +13,15 @@ import $ from 'jquery';
import { notEmpty } from '@ember/object/computed'; import { notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import TooltipMixin from '../../mixins/tooltip';
import Notifier from '../../mixins/notifier'; import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(TooltipMixin, Notifier, { export default Component.extend(Notifier, {
documentService: service('document'), documentService: service('document'),
sectionService: service('section'), sectionService: service('section'),
store: service(), store: service(),
appMeta: service(), appMeta: service(),
link: service(), linkSvc: service('link'),
hasPages: notEmpty('pages'), hasPages: notEmpty('pages'),
showInsertSectionModal: false, showInsertSectionModal: false,
newSectionLocation: '', newSectionLocation: '',
@ -41,36 +40,21 @@ export default Component.extend(TooltipMixin, Notifier, {
this.set('showLikes', this.get('folder.allowLikes') && this.get('document.isLive')); this.set('showLikes', this.get('folder.allowLikes') && this.get('document.isLive'));
}, },
didRender() {
this._super(...arguments);
this.contentLinkHandler();
},
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
if (this.get('session.authenticated')) {
this.renderTooltips();
}
this.jumpToSection(this.get('currentPageId')); this.jumpToSection(this.get('currentPageId'));
},
willDestroyElement() { this.contentLinkHandler();
this._super(...arguments);
if (this.get('session.authenticated')) {
this.removeTooltips();
}
}, },
contentLinkHandler() { contentLinkHandler() {
let links = this.get('link'); let linkSvc = this.get('linkSvc');
let doc = this.get('document'); let doc = this.get('document');
let self = this; let self = this;
$("a[data-documize='true']").off('click').on('click', function (e) { $("a[data-documize='true']").off('click').on('click', function (e) {
let link = links.getLinkObject(self.get('links'), this); let link = linkSvc.getLinkObject(self.get('links'), this);
// local link? exists? // local link? exists?
if ((link.linkType === "section" || link.linkType === "tab") && link.documentId === doc.get('id')) { if ((link.linkType === "section" || link.linkType === "tab") && link.documentId === doc.get('id')) {
@ -92,7 +76,10 @@ export default Component.extend(TooltipMixin, Notifier, {
return false; return false;
} }
links.linkClick(doc, link); e.preventDefault();
e.stopPropagation();
linkSvc.linkClick(doc, link);
return false; return false;
}); });
}, },

View file

@ -9,19 +9,15 @@
// //
// https://documize.com // https://documize.com
import { computed, set } from '@ember/object'; import { computed } from '@ember/object';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, { export default Component.extend(ModalMixin, {
documentService: service('document'), documentService: service('document'),
revision: null,
revisions: null,
diff: '', diff: '',
hasRevisions: computed('revisions', function() { revision: null,
return this.get('revisions').length > 0;
}),
hasDiff: computed('diff', function() { hasDiff: computed('diff', function() {
return this.get('diff').length > 0; return this.get('diff').length > 0;
}), }),
@ -34,32 +30,17 @@ export default Component.extend(ModalMixin, {
this.get('document.protection') === constants.ProtectionType.None; this.get('document.protection') === constants.ProtectionType.None;
}), }),
init() {
this._super(...arguments);
this.revisions = [];
},
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
this.fetchRevisions();
},
fetchRevisions() { let revision = this.get('revision');
this.get('documentService').getDocumentRevisions(this.get('document.id')).then((revisions) => {
revisions.forEach((r) => {
set(r, 'deleted', r.revisions === 0);
let date = moment(r.created).format('Do MMMM YYYY HH:mm');
let format = `${r.firstname} ${r.lastname} on ${date} changed ${r.title}`;
set(r, 'label', format);
});
this.set('revisions', revisions); if (is.not.null(revision)) {
if (!revision.deleted) {
if (revisions.length > 0 && is.null(this.get('revision'))) { this.fetchDiff(revision.pageId, revision.id);
this.send('onSelectRevision', revisions[0]);
} }
}); }
}, },
fetchDiff(pageId, revisionId) { fetchDiff(pageId, revisionId) {
this.get('documentService').getPageRevisionDiff(this.get('document.id'), pageId, revisionId).then((revision) => { this.get('documentService').getPageRevisionDiff(this.get('document.id'), pageId, revisionId).then((revision) => {
@ -68,12 +49,8 @@ export default Component.extend(ModalMixin, {
}, },
actions: { actions: {
onSelectRevision(revision) { onShowModal() {
this.set('revision', revision); this.modalOpen('#document-rollback-modal', {show:true});
if (!revision.deleted) {
this.fetchDiff(revision.pageId, revision.id);
}
}, },
onRollback() { onRollback() {

View file

@ -0,0 +1,16 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Component from '@ember/component';
export default Component.extend({
classNames: ['categories'],
});

View file

@ -12,6 +12,8 @@
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend({
classNames: ['hashtags'],
init() { init() {
this._super(...arguments); this._super(...arguments);
let tagz = []; let tagz = [];
@ -20,7 +22,7 @@ export default Component.extend({
let tags = this.get('documentTags').split('#'); let tags = this.get('documentTags').split('#');
_.each(tags, function(tag) { _.each(tags, function(tag) {
if (tag.length > 0) { if (tag.length > 0) {
tagz.pushObject("#" + tag); tagz.pushObject(tag);
} }
}); });
} }

View file

@ -11,10 +11,9 @@
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { A } from '@ember/array'; import { A } from '@ember/array';
import TooltipMixin from '../../mixins/tooltip';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(TooltipMixin, { export default Component.extend({
showDeleteDialog: false, showDeleteDialog: false,
showMoveDialog: false, showMoveDialog: false,
selectedDocuments: A([]), selectedDocuments: A([]),
@ -105,7 +104,6 @@ export default Component.extend(TooltipMixin, {
this.set('selectedCaption', list.length > 1 ? 'documents' : 'document'); this.set('selectedCaption', list.length > 1 ? 'documents' : 'document');
this.set('selectedDocuments', A(list)); this.set('selectedDocuments', A(list));
this.renderTooltips();
} }
} }
}); });

View file

@ -52,11 +52,9 @@ export default Component.extend(AuthMixin, Notifier, {
let id = this.get('deleteBlockId'); let id = this.get('deleteBlockId');
this.showWait();
this.get('sectionSvc').deleteBlock(id).then(() => { this.get('sectionSvc').deleteBlock(id).then(() => {
this.set('deleteBlockId', ''); this.set('deleteBlockId', '');
this.showDone(); this.notifySuccess('Deleted');
this.get('sectionSvc').getSpaceBlocks(this.get('space.id')).then((blocks) => { this.get('sectionSvc').getSpaceBlocks(this.get('space.id')).then((blocks) => {
this.set('blocks', blocks); this.set('blocks', blocks);

View file

@ -12,19 +12,19 @@
import $ from 'jquery'; import $ from 'jquery';
import { A } from '@ember/array'; import { A } from '@ember/array';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Notifer from '../../mixins/notifier'; import Notifer from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, Notifer, { export default Component.extend(ModalMixin, Notifer, {
spaceSvc: service('folder'), spaceSvc: service('folder'),
groupSvc: service('group'), groupSvc: service('group'),
categorySvc: service('category'), categorySvc: service('category'),
appMeta: service(), appMeta: service(),
store: service(), store: service(),
editId: '',
editName: '',
deleteId: '', deleteId: '',
dropdown: null,
newCategory: '', newCategory: '',
init() { init() {
@ -34,13 +34,11 @@ export default Component.extend(ModalMixin, TooltipMixin, Notifer, {
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
this.renderTooltips();
this.load(); this.load();
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.removeTooltips();
}, },
load() { load() {
@ -120,13 +118,20 @@ export default Component.extend(ModalMixin, TooltipMixin, Notifer, {
spaceId: this.get('space.id') spaceId: this.get('space.id')
}; };
this.showWait();
this.get('categorySvc').add(c).then(() => { this.get('categorySvc').add(c).then(() => {
this.load(); this.load();
this.showDone(); this.notifySuccess('Category added');
}); });
}, },
onShowEdit(id) {
let cat = this.get('category').findBy('id', id);
this.set('editId', cat.get('id'));
this.set('editName', cat.get('category'));
this.modalOpen('#category-edit-modal', {show: true}, "#edit-category-id");
},
onShowDelete(id) { onShowDelete(id) {
let cat = this.get('category').findBy('id', id); let cat = this.get('category').findBy('id', id);
this.set('deleteId', cat.get('id')); this.set('deleteId', cat.get('id'));
@ -142,32 +147,22 @@ export default Component.extend(ModalMixin, TooltipMixin, Notifer, {
}); });
}, },
onEdit(id) { onSave() {
this.setEdit(id, true); let name = this.get('editName');
this.removeTooltips(); if (name === '') {
}, $('#edit-category-name').addClass('is-invalid').focus();
onEditCancel(id) {
this.setEdit(id, false);
this.load();
this.renderTooltips();
},
onSave(id) {
let cat = this.setEdit(id, true);
if (cat.get('category') === '') {
$('#edit-category-' + cat.get('id')).addClass('is-invalid').focus();
return false; return false;
} }
cat = this.setEdit(id, false); let cat = this.get('category').findBy('id', this.get('editId'));
$('#edit-category-' + cat.get('id')).removeClass('is-invalid'); cat.set('category', name);
this.modalClose('#category-edit-modal');
$('#edit-category-name').removeClass('is-invalid');
this.get('categorySvc').save(cat).then(() => { this.get('categorySvc').save(cat).then(() => {
this.load(); this.load();
}); });
this.renderTooltips();
}, },
onShowAccessPicker(catId) { onShowAccessPicker(catId) {

View file

@ -36,9 +36,10 @@ export default Component.extend(AuthMixin, Notifier, {
$("#delete-space-name").removeClass("is-invalid"); $("#delete-space-name").removeClass("is-invalid");
this.get('spaceSvc').delete(this.get('space.id')).then(() => { /* jshint ignore:line */ this.get('spaceSvc').delete(this.get('space.id')).then(() => { /* jshint ignore:line */
this.get('localStorage').clearSessionItem('folder');
this.get('router').transitionTo('folders');
}); });
this.get('localStorage').clearSessionItem('folder');
this.get('router').transitionTo('folders');
} }
} }
}); });

View file

@ -11,7 +11,6 @@
import { A } from '@ember/array'; import { A } from '@ember/array';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { schedule } from '@ember/runloop';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { empty } from '@ember/object/computed'; import { empty } from '@ember/object/computed';
import AuthMixin from '../../mixins/auth'; import AuthMixin from '../../mixins/auth';
@ -21,18 +20,29 @@ import Component from '@ember/component';
export default Component.extend(AuthMixin, Notifier, { export default Component.extend(AuthMixin, Notifier, {
router: service(), router: service(),
spaceSvc: service('folder'), spaceSvc: service('folder'),
iconSvc: service('icon'),
localStorage: service('localStorage'), localStorage: service('localStorage'),
isSpaceAdmin: computed('permissions', function() { isSpaceAdmin: computed('permissions', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}), }),
spaceName: '', spaceName: '',
hasNameError: empty('spaceName'), hasNameError: empty('spaceName'),
spaceTypeOptions: A([]), spaceTypeOptions: A([]),
spaceType: 0, spaceType: 0,
likes: '', likes: '',
allowLikes: false, allowLikes: false,
spaceLifecycleOptions: A([]),
spaceLifecycle: null,
iconList: A([]),
spaceIcon: '',
spaceDesc: '',
spaceLabel: '',
init() {
this._super(...arguments);
this.set('iconList', this.get('iconSvc').getSpaceIconList());
},
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
@ -56,6 +66,15 @@ export default Component.extend(AuthMixin, Notifier, {
} }
this.set('spaceName', this.get('space.name')); this.set('spaceName', this.get('space.name'));
this.set('spaceDesc', this.get('space.desc'));
this.set('spaceLabel', this.get('space.labelId'));
let icon = this.get('space.icon');
if (is.empty(icon)) {
icon = constants.IconMeta.Apps;
}
this.set('spaceIcon', icon);
}, },
actions: { actions: {
@ -63,12 +82,16 @@ export default Component.extend(AuthMixin, Notifier, {
this.set('spaceType', t); this.set('spaceType', t);
}, },
onSetLikes(l) { onSetSpaceLifecycle(l) {
this.set('allowLikes', l); this.set('spaceLifecycle', l);
},
schedule('afterRender', () => { onSetIcon(icon) {
if (l) this.$('#space-likes-prompt').focus(); this.set('spaceIcon', icon);
}); },
onSetLabel(id) {
this.set('spaceLabel', id);
}, },
onSave() { onSave() {
@ -84,10 +107,12 @@ export default Component.extend(AuthMixin, Notifier, {
if (spaceName.length === 0) return; if (spaceName.length === 0) return;
space.set('name', spaceName); space.set('name', spaceName);
this.showWait(); space.set('icon', this.get('spaceIcon'));
space.set('desc', this.get('spaceDesc'));
space.set('labelId', this.get('spaceLabel'));
this.get('spaceSvc').save(space).then(() => { this.get('spaceSvc').save(space).then(() => {
this.showDone(); this.notifySuccess('Saved');
}); });
} }
} }

View file

@ -30,6 +30,8 @@ export default Component.extend(Notifier, Modals, {
searchText: '', searchText: '',
inviteEmail: '', inviteEmail: '',
inviteMessage: '', inviteMessage: '',
showSpacePermExplain: false,
showDocumentPermExplain: false,
isSpaceAdmin: computed('permissions', function() { isSpaceAdmin: computed('permissions', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
@ -132,8 +134,6 @@ export default Component.extend(Notifier, Modals, {
let spacePermissions = this.get('spacePermissions'); let spacePermissions = this.get('spacePermissions');
let filteredUsers = A([]); let filteredUsers = A([]);
this.showWait();
this.get('userSvc').matchUsers(s).then((users) => { this.get('userSvc').matchUsers(s).then((users) => {
users.forEach((user) => { users.forEach((user) => {
let exists = spacePermissions.findBy('whoId', user.get('id')); let exists = spacePermissions.findBy('whoId', user.get('id'));
@ -144,11 +144,30 @@ export default Component.extend(Notifier, Modals, {
}); });
this.set('filteredUsers', filteredUsers); this.set('filteredUsers', filteredUsers);
this.showDone();
}); });
}, },
actions: { actions: {
toggleSpacePerms() {
this.set('showSpacePermExplain', !this.get('showSpacePermExplain'));
if (this.showSpacePermExplain) {
this.$(".space-perms").show();
} else {
this.$(".space-perms").hide();
}
},
toggleDocumentPerms() {
this.set('showDocumentPermExplain', !this.get('showDocumentPermExplain'));
if (this.showDocumentPermExplain) {
this.$(".document-perms").show();
} else {
this.$(".document-perms").hide();
}
},
onShowInviteModal() { onShowInviteModal() {
this.modalOpen("#space-invite-user-modal", {"show": true}, '#space-invite-email'); this.modalOpen("#space-invite-user-modal", {"show": true}, '#space-invite-email');
}, },
@ -160,9 +179,7 @@ export default Component.extend(Notifier, Modals, {
onSave() { onSave() {
if (!this.get('isSpaceAdmin')) return; if (!this.get('isSpaceAdmin')) return;
this.showWait(); let message = this.getDefaultInvitationMessage();
let message = this.getDefaultInvitationMessage();
let permissions = this.get('spacePermissions'); let permissions = this.get('spacePermissions');
let folder = this.get('folder'); let folder = this.get('folder');
let payload = { Message: message, Permissions: permissions }; let payload = { Message: message, Permissions: permissions };
@ -197,7 +214,7 @@ export default Component.extend(Notifier, Modals, {
} }
this.get('spaceSvc').savePermissions(folder.get('id'), payload).then(() => { this.get('spaceSvc').savePermissions(folder.get('id'), payload).then(() => {
this.showDone(); this.notifySuccess('Saved');
this.get('onRefresh')(); this.get('onRefresh')();
}); });
}, },
@ -219,17 +236,13 @@ export default Component.extend(Notifier, Modals, {
let spacePermissions = this.get('spacePermissions'); let spacePermissions = this.get('spacePermissions');
let constants = this.get('constants'); let constants = this.get('constants');
this.showWait(); let exists = spacePermissions.findBy('whoId', user.get('id'));
let exists = spacePermissions.findBy('whoId', user.get('id'));
if (is.undefined(exists)) { if (is.undefined(exists)) {
spacePermissions.pushObject(this.permissionRecord(constants.WhoType.User, user.get('id'), user.get('fullname'))); spacePermissions.pushObject(this.permissionRecord(constants.WhoType.User, user.get('id'), user.get('fullname')));
this.set('spacePermissions', spacePermissions); this.set('spacePermissions', spacePermissions);
this.send('onSearch'); this.send('onSearch');
} }
this.showDone();
}, },
onSpaceInvite(e) { onSpaceInvite(e) {
@ -248,9 +261,7 @@ export default Component.extend(Notifier, Modals, {
return; return;
} }
this.showWait(); var result = {
var result = {
Message: message, Message: message,
Recipients: [] Recipients: []
}; };
@ -271,7 +282,7 @@ export default Component.extend(Notifier, Modals, {
this.set('inviteEmail', ''); this.set('inviteEmail', '');
this.get('spaceSvc').share(this.get('folder.id'), result).then(() => { this.get('spaceSvc').share(this.get('folder.id'), result).then(() => {
this.showDone(); this.notifySuccess('Invites sent');
this.$('#space-invite-email').removeClass('is-invalid'); this.$('#space-invite-email').removeClass('is-invalid');
this.modalClose("#space-invite-user-modal"); this.modalClose("#space-invite-user-modal");
this.load(); this.load();

View file

@ -1,37 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import stringUtil from '../../utils/string';
import AuthMixin from '../../mixins/auth';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component';
export default Component.extend(AuthMixin, Notifier, {
spaceSvc: service('folder'),
isSpaceAdmin: computed('permissions', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
actions: {
onOpenTemplate(id) {
if (is.empty(id)) {
return;
}
let template = this.get('templates').findBy('id', id)
let slug = stringUtil.makeSlug(template.get('title'));
this.get('router').transitionTo('document', this.get('space.id'), this.get('space.slug'), id, slug);
}
}
});

View file

@ -17,6 +17,7 @@ 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, {
classNames: ["section"],
router: service(), router: service(),
documentService: service('document'), documentService: service('document'),
folderService: service('folder'), folderService: service('folder'),
@ -26,6 +27,8 @@ export default Component.extend(AuthMixin, {
spaceSettings: computed('permissions', function() { spaceSettings: computed('permissions', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}), }),
selectedFilter: '',
spaceLabel: null,
init() { init() {
this._super(...arguments); this._super(...arguments);
@ -53,6 +56,7 @@ 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', _.findWhere(this.get('labels'), {id: this.get('space.labelId')}));
schedule('afterRender', () => { schedule('afterRender', () => {
if (this.get('categoryFilter') !== '') { if (this.get('categoryFilter') !== '') {
@ -81,8 +85,6 @@ export default Component.extend(AuthMixin, {
}); });
this.set('categoryFilter', id); this.set('categoryFilter', id);
this.set('spaceSelected', false);
this.set('uncategorizedSelected', false);
break; break;
case 'uncategorized': case 'uncategorized':
@ -94,19 +96,29 @@ export default Component.extend(AuthMixin, {
}); });
this.set('categoryFilter', ''); this.set('categoryFilter', '');
this.set('spaceSelected', false);
this.set('uncategorizedSelected', true);
break; break;
case 'space': case 'space':
allowed = _.pluck(categoryMembers, 'documentId');
docs.forEach((d) => { docs.forEach((d) => {
filtered.pushObject(d); filtered.pushObject(d);
}); });
this.set('categoryFilter', ''); this.set('categoryFilter', '');
this.set('spaceSelected', true); break;
this.set('uncategorizedSelected', false);
case 'template':
filtered.pushObjects(this.get('templates'));
this.set('categoryFilter', '');
break;
case 'draft':
filtered = this.get('documentsDraft');
this.set('categoryFilter', '');
break;
case 'live':
filtered = this.get('documentsLive');
this.set('categoryFilter', '');
break; break;
} }
@ -114,6 +126,7 @@ export default Component.extend(AuthMixin, {
cat.set('selected', cat.get('id') === id); cat.set('selected', cat.get('id') === id);
}); });
this.set('selectedFilter', filter);
this.set('categories', categories); this.set('categories', categories);
this.get('onFiltered')(filtered); this.get('onFiltered')(filtered);
} }

View file

@ -13,13 +13,13 @@ import $ from 'jquery';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { schedule } from '@ember/runloop'; import { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import AuthMixin from '../../mixins/auth'; import AuthMixin from '../../mixins/auth';
import Notifier from '../../mixins/notifier'; import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, { export default Component.extend(ModalMixin, AuthMixin, Notifier, {
classNames: ["display-inline-block"],
spaceService: service('folder'), spaceService: service('folder'),
localStorage: service(), localStorage: service(),
templateService: service('template'), templateService: service('template'),
@ -75,7 +75,6 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
this.set('pinState.pinId', pinId); this.set('pinState.pinId', pinId);
this.set('pinState.isPinned', pinId !== ''); this.set('pinState.isPinned', pinId !== '');
this.set('pinState.newName', folder.get('name')); this.set('pinState.newName', folder.get('name'));
this.renderTooltips();
}); });
let cats = this.get('categories'); let cats = this.get('categories');
@ -92,7 +91,6 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.removeTooltips();
if (is.not.null(this.get('dropzone'))) { if (is.not.null(this.get('dropzone'))) {
this.get('dropzone').destroy(); this.get('dropzone').destroy();
@ -152,11 +150,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
actions: { actions: {
onUnpin() { onUnpin() {
this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => { this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => {
$('#space-pin-button').tooltip('dispose');
this.set('pinState.isPinned', false); this.set('pinState.isPinned', false);
this.set('pinState.pinId', ''); this.set('pinState.pinId', '');
this.eventBus.publish('pinChange'); this.eventBus.publish('pinChange');
this.renderTooltips();
}); });
}, },
@ -168,11 +164,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
}; };
this.get('pinned').pinItem(pin).then((pin) => { this.get('pinned').pinItem(pin).then((pin) => {
$('#space-pin-button').tooltip('dispose');
this.set('pinState.isPinned', true); this.set('pinState.isPinned', true);
this.set('pinState.pinId', pin.get('id')); this.set('pinState.pinId', pin.get('id'));
this.eventBus.publish('pinChange'); this.eventBus.publish('pinChange');
this.renderTooltips();
}); });
return true; return true;
@ -288,8 +282,6 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
}, },
onExport() { onExport() {
this.showWait();
let spec = { let spec = {
spaceId: this.get('space.id'), spaceId: this.get('space.id'),
data: [], data: [],
@ -310,7 +302,7 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
this.get('documentSvc').export(spec).then((htmlExport) => { this.get('documentSvc').export(spec).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, this.get('space.slug') + '.html'); this.get('browserSvc').downloadFile(htmlExport, this.get('space.slug') + '.html');
this.showDone(); this.notifySuccess('Exported');
}); });
this.modalClose("#space-export-modal"); this.modalClose("#space-export-modal");

View file

@ -1,62 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import $ from 'jquery';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
export default Component.extend({
classNames: ['layout-footer', 'non-printable'],
tagName: 'footer',
appMeta: service(),
showWait: false,
showDone: false,
showMessage: false,
message: '',
init() {
this._super(...arguments);
this.eventBus.subscribe('notifyUser', this, 'processNotification');
},
processNotification(msg) {
if (this.get('isDestroyed') || this.get('isDestroying')) return;
if (msg === 'wait') {
this.set('showWait', true);
this.set('showMessage', false);
this.set('showDone', false);
}
if (msg === 'done') {
$('.progress-done').removeClass('zoomOut').addClass('zoomIn');
this.set('showWait', false);
this.set('showMessage', false);
this.set('showDone', true);
setTimeout(function() {
$('.progress-done').removeClass('zoomIn').addClass('zoomOut');
}, 3000);
}
if (msg !== 'done' && msg !== 'wait') {
$('.progress-notification').removeClass('zoomOut').addClass('zoomIn');
this.set('showWait', false);
this.set('showDone', false);
this.set('showMessage', true);
this.set('message', msg);
setTimeout(function() {
$('.progress-notification').removeClass('zoomIn').addClass('zoomOut');
}, 3000);
}
}
});

View file

@ -0,0 +1,28 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import { inject as service } from '@ember/service';
import Component from '@ember/component';
export default Component.extend({
appMeta: service(),
icon: null,
meta: null,
logo: false,
didReceiveAttrs() {
this._super(...arguments);
if (this.get('logo')) {
let cb = + new Date();
this.set('cacheBuster', cb);
}
}
});

View file

@ -12,21 +12,14 @@
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend({
tagName: 'nav', tagName: 'div',
classNames: ['layout-sidebar', 'non-printable'], classNames: ['master-container'],
classNameBindings: ['scrollable:sidebar-scroll'],
scrollable: false,
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
// let sb = this.$().overlayScrollbars({ scrollbars: { autoHide: 'leave' }});
// this.set('scrollbars', sb);
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
// let sb = this.get('scrollbars');
// sb.destroy();
} }
}); });

View file

@ -11,14 +11,14 @@
import $ from 'jquery'; import $ from 'jquery';
import { notEmpty } from '@ember/object/computed'; import { notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service' import { inject as service } from '@ember/service';
import ModalMixin from '../../mixins/modal'; import Modals from '../../mixins/modal';
import TooltipMixin from '../../mixins/tooltip';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, { export default Component.extend(Modals, {
classNames: ['layout-header', 'non-printable'], tagName: 'div',
tagName: 'header', classNames: ['master-sidebar-container', 'non-printable'],
selectedItem: '',
folderService: service('folder'), folderService: service('folder'),
appMeta: service(), appMeta: service(),
session: service(), session: service(),
@ -30,9 +30,11 @@ export default Component.extend(ModalMixin, TooltipMixin, {
hasDocumentPins: notEmpty('documentPins'), hasDocumentPins: notEmpty('documentPins'),
hasWhatsNew: false, hasWhatsNew: false,
newsContent: '', newsContent: '',
hideNavigation: false,
init() { init() {
this._super(...arguments); this._super(...arguments);
let constants = this.get('constants'); let constants = this.get('constants');
this.pins = []; this.pins = [];
@ -71,7 +73,15 @@ export default Component.extend(ModalMixin, TooltipMixin, {
this.setupPins(); this.setupPins();
} }
this.renderTooltips(); this.eventBus.subscribe('notifyUser', this, 'processNotification');
},
willDestroyElement() {
this._super(...arguments);
this.eventBus.unsubscribe('notifyUser');
this.eventBus.unsubscribe('pinChange');
iziToast.destroy();
}, },
setupPins() { setupPins() {
@ -87,11 +97,39 @@ export default Component.extend(ModalMixin, TooltipMixin, {
}); });
}, },
willDestroyElement() { processNotification(msg, type) {
this._super(...arguments); if (this.get('isDestroyed') || this.get('isDestroying')) return;
this.removeTooltips(); if (is.not.undefined(type)) {
this.eventBus.unsubscribe('pinChange'); switch (type) {
case 'info':
iziToast.info({
title: '',
message: msg,
});
break;
case 'success':
iziToast.success({
title: '',
message: msg,
});
break;
case 'warn':
iziToast.warning({
title: '',
message: msg,
});
break;
case 'error':
iziToast.error({
title: '',
message: msg,
});
break;
}
return;
}
}, },
actions: { actions: {
@ -110,12 +148,11 @@ export default Component.extend(ModalMixin, TooltipMixin, {
} }
}, },
onShowWhatsNewModal() { onNew() {
this.modalOpen("#whats-new-modal", { "show": true });
if (this.get('newsContent.length') > 0) { if (this.get('newsContent.length') > 0) {
this.get('session').seenNewVersion(); this.get('session').seenNewVersion();
this.set('hasWhatsNew', false); this.set('hasWhatsNew', false);
this.get('router').transitionTo('updates');
} }
}, },

View file

@ -13,6 +13,6 @@
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend({
classNames: ['layout-body'], tagName: 'p',
tagName: 'main' classNames: ['master-page-desc'],
}); });

View file

@ -13,6 +13,6 @@
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend({
classNames: ['layout-content'], tagName: 'h1',
tagName: 'article' classNames: ['master-page-heading'],
}); });

View file

@ -95,11 +95,14 @@ export default Component.extend({
} }
if ($("#stage-2-password-confirm").val() !== $("#stage-2-password").val()) { if ($("#stage-2-password-confirm").val() !== $("#stage-2-password").val()) {
$(".mismatch").show(); $("#stage-2-password").addClass("is-invalid");
$("#stage-2-password-confirm").addClass("is-invalid");
// $(".mismatch").show();
// $(".password-status").attr("src", "/assets/img/onboard/lock-red.png"); // $(".password-status").attr("src", "/assets/img/onboard/lock-red.png");
return; return;
} }
$("#stage-2-password").removeClass("is-invalid");
$("#stage-2-password-confirm").removeClass("is-invalid"); $("#stage-2-password-confirm").removeClass("is-invalid");
self.set('processing', false); self.set('processing', false);
@ -121,7 +124,7 @@ export default Component.extend({
let creds = { password: password, email: user.email }; let creds = { password: password, email: user.email };
self.get('session').authenticate('authenticator:documize', creds).then(() => { self.get('session').authenticate('authenticator:documize', creds).then(() => {
window.location.href = 's/' + self.folderId + "/" + self.slug; window.location.href = '//' + window.location.host + '/s/' + self.folderId + "/" + self.slug;
}); });
// var credentials = encodingUtil.Base64.encode(netUtil.getSubdomain() + ":" + user.email + ":" + password); // var credentials = encodingUtil.Base64.encode(netUtil.getSubdomain() + ":" + user.email + ":" + password);

View file

@ -12,11 +12,10 @@
import $ from 'jquery'; import $ from 'jquery';
import { empty } from '@ember/object/computed'; import { empty } from '@ember/object/computed';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(TooltipMixin, ModalMixin, { export default Component.extend(ModalMixin, {
busy: false, busy: false,
mousetrap: null, mousetrap: null,
showLinkModal: false, showLinkModal: false,
@ -57,15 +56,11 @@ export default Component.extend(TooltipMixin, ModalMixin, {
$('#' + this.get('pageId')).focus(function() { $('#' + this.get('pageId')).focus(function() {
$(this).select(); $(this).select();
}); });
this.renderTooltips();
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.removeTooltips();
let mousetrap = this.get('mousetrap'); let mousetrap = this.get('mousetrap');
if (is.not.null(mousetrap)) { if (is.not.null(mousetrap)) {
mousetrap.unbind('esc'); mousetrap.unbind('esc');

View file

@ -10,10 +10,9 @@
// https://documize.com // https://documize.com
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import TooltipMixin from '../../../mixins/tooltip';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(TooltipMixin, { export default Component.extend({
isDirty: false, isDirty: false,
pageBody: "", pageBody: "",
codeSyntax: null, codeSyntax: null,
@ -100,9 +99,6 @@ export default Component.extend(TooltipMixin, {
editor = null; editor = null;
this.set('codeEditor', null); this.set('codeEditor', null);
} }
this.removeTooltips();
}, },
// Wrap code in PRE tag with language identifier for subsequent rendering. // Wrap code in PRE tag with language identifier for subsequent rendering.

View file

@ -13,11 +13,10 @@ import $ from 'jquery';
import { set } from '@ember/object'; import { set } from '@ember/object';
import { schedule } from '@ember/runloop'; import { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component';
import SectionMixin from '../../../mixins/section'; import SectionMixin from '../../../mixins/section';
import TooltipMixin from '../../../mixins/tooltip'; import Component from '@ember/component';
export default Component.extend(SectionMixin, TooltipMixin, { export default Component.extend(SectionMixin, {
sectionService: service('section'), sectionService: service('section'),
isDirty: false, isDirty: false,
waiting: false, waiting: false,
@ -27,7 +26,7 @@ export default Component.extend(SectionMixin, TooltipMixin, {
this._super(...arguments); this._super(...arguments);
this.user = {}; this.user = {};
this.workspaces = []; this.workspaces = [];
this.config = {}; this.config = {};
}, },
didReceiveAttrs() { didReceiveAttrs() {
@ -92,7 +91,6 @@ export default Component.extend(SectionMixin, TooltipMixin, {
schedule('afterRender', () => { schedule('afterRender', () => {
window.scrollTo(0, document.body.scrollHeight); window.scrollTo(0, document.body.scrollHeight);
self.renderTooltips();
}); });
self.set('waiting', false); self.set('waiting', false);
}, function (reason) { // eslint-disable-line no-unused-vars }, function (reason) { // eslint-disable-line no-unused-vars

View file

@ -10,11 +10,10 @@
// https://documize.com // https://documize.com
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component';
import SectionMixin from '../../../mixins/section'; import SectionMixin from '../../../mixins/section';
import TooltipMixin from '../../../mixins/tooltip'; import Component from '@ember/component';
export default Component.extend(SectionMixin, TooltipMixin, { export default Component.extend(SectionMixin, {
sectionService: service('section'), sectionService: service('section'),
isDirty: false, isDirty: false,
waiting: false, waiting: false,

View file

@ -33,7 +33,7 @@ export default Component.extend({
init() { init() {
this._super(...arguments); this._super(...arguments);
let body = (is.not.undefined(this.get('meta'))) ? this.get('meta.rawBody').trim() : ''; let body = (is.not.undefined(this.get('meta'))) ? this.get('meta.rawBody').trim() : '';
this.set('pageBody', body); this.set('pageBody', body);
}, },
didInsertElement() { didInsertElement() {
@ -67,7 +67,7 @@ export default Component.extend({
dragDrop: false, dragDrop: false,
extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"} extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"}
}); });
CodeMirror.commands.save = function(/*instance*/){ CodeMirror.commands.save = function(/*instance*/){
Mousetrap.trigger('ctrl+s'); Mousetrap.trigger('ctrl+s');
}; };

View file

@ -14,12 +14,11 @@ import $ from 'jquery';
import { htmlSafe } from '@ember/string'; import { htmlSafe } from '@ember/string';
import { computed, set } from '@ember/object'; import { computed, set } from '@ember/object';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component';
import NotifierMixin from '../../../mixins/notifier'; import NotifierMixin from '../../../mixins/notifier';
import TooltipMixin from '../../../mixins/tooltip';
import SectionMixin from '../../../mixins/section'; import SectionMixin from '../../../mixins/section';
import Component from '@ember/component';
export default Component.extend(SectionMixin, NotifierMixin, TooltipMixin, { export default Component.extend(SectionMixin, NotifierMixin, {
sectionService: service('section'), sectionService: service('section'),
isDirty: false, isDirty: false,
busy: false, busy: false,
@ -93,10 +92,6 @@ export default Component.extend(SectionMixin, NotifierMixin, TooltipMixin, {
}); });
}, },
willDestroyElement() {
this.removeTooltips();
},
getBoardLists() { getBoardLists() {
this.set('busy', true); this.set('busy', true);

Some files were not shown because too many files have changed in this diff Show more