1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-24 15:49:44 +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?
All spaces.
![Documize](screenshot-1.png "Documize")
Space view.
![Documize](screenshot-2.png "Documize")
## 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
@ -99,8 +93,8 @@ Documize supports the following (evergreen) browsers:
Documize is built with the following technologies:
- EmberJS (v3.1.2)
- Go (v1.11.2)
- EmberJS (v3.5.1)
- Go (v1.11.4)
## Authentication Options

View file

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

View file

@ -8,23 +8,26 @@ echo "Build process started $NOW"
echo "Building Ember assets..."
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..."
cd ..
rm -rf embed/bindata/public
mkdir -p 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/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/tinymce embed/bindata/public/tinymce
cp gui/dist-prod/*.* embed/bindata
cp gui/dist-prod/favicon.ico embed/bindata/public
cp gui/dist-prod/manifest.json embed/bindata/public
rm -rf embed/bindata/mail
mkdir -p embed/bindata/mail
cp domain/mail/*.html embed/bindata/mail
cp core/database/templates/*.html embed/bindata
rm -rf embed/bindata/scripts
mkdir -p embed/bindata/scripts
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 (
"bytes"
"errors"
"fmt"
"os/exec"
"time"
"github.com/documize/community/core/log"
)
var errTimeout = errors.New("conversion timelimit exceeded")
@ -39,7 +38,7 @@ func CommandWithTimeout(command *exec.Cmd, timeout time.Duration) ([]byte, error
select {
case <-time.After(timeout):
if err := command.Process.Kill(); err != nil {
log.Error("failed to kill: ", err)
fmt.Errorf("failed to kill: ", err)
}
<-done // prevent memory leak
//fmt.Println("DEBUG timeout")

View file

@ -15,9 +15,12 @@ import (
"bytes"
"database/sql"
"fmt"
"github.com/documize/community/domain/auth"
"github.com/documize/community/model/space"
"io"
"mime"
"net/http"
"strings"
"github.com/documize/community/core/env"
"github.com/documize/community/core/request"
@ -47,9 +50,22 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
method := "attachment.Download"
ctx := domain.GetRequestContext(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 {
response.WriteNotFoundError(w, method, request.Param(r, "fileID"))
return
@ -60,6 +76,99 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
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)
if typ == "" {
typ = "application/octet-stream"

View file

@ -46,7 +46,6 @@ import (
"github.com/documize/community/model/doc"
"github.com/documize/community/model/group"
"github.com/documize/community/model/link"
"github.com/documize/community/model/org"
"github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"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)
}
o := []org.Organization{}
o := []orgExtended{}
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_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_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)
if err != nil {
return
@ -255,10 +254,6 @@ func (b backerHandler) dmzOrg(files *[]backupItem) (err error) {
// Config, User Config.
func (b backerHandler) dmzConfig(files *[]backupItem) (err error) {
type config struct {
ConfigKey string `json:"key"`
ConfigValue string `json:"config"`
}
c := []config{}
err = b.Runtime.Db.Select(&c, `SELECT c_key AS configkey, c_config AS configvalue FROM dmz_config`)
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)
}
type userConfig struct {
OrgID string `json:"orgId"`
UserID string `json:"userId"`
ConfigKey string `json:"key"`
ConfigValue string `json:"config"`
}
uc := []userConfig{}
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)
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,
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_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
FROM dmz_space`+w)
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})
// 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{}
err = b.Runtime.Db.Select(&vt, `
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})
// 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{}
err = b.Runtime.Db.Select(&cm, `
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})
// 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{}
err = b.Runtime.Db.Select(&sh, `
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/group"
"github.com/documize/community/model/link"
"github.com/documize/community/model/org"
"github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"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) {
filename := "dmz_org.json"
org := []org.Organization{}
org := []orgExtended{}
err = r.fileJSON(filename, &org)
if err != nil {
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(`
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_maxtags, c_verified, c_serial, c_sub, c_active, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
c_maxtags, c_verified, c_serial, c_sub, c_active,
c_theme, c_logo, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
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),
org[i].AllowAnonymousAccess, org[i].AuthProvider, org[i].AuthConfig,
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)
if err != nil {
r.Context.Transaction.Rollback()
@ -402,6 +403,7 @@ func (r *restoreHandler) dmzOrg() (err error) {
org[0].Serial = r.Spec.Org.Serial
org[0].Title = r.Spec.Org.Title
org[0].Subscription = r.Spec.Org.Subscription
org[0].Theme = r.Spec.Org.Theme
}
_, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET
@ -612,8 +614,16 @@ func (r *restoreHandler) dmzSpace() (err error) {
}
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 (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
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)
_, 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_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 {
r.Context.Transaction.Rollback()

View file

@ -105,6 +105,14 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
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()
cat, err = h.Store.Category.Get(ctx, cat.RefID)
@ -295,6 +303,14 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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()
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
FROM dmz_category
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
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='category' AND (r.c_userid=? OR r.c_userid='0')
))
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='category' AND (r.c_userid=? OR r.c_userid='0')
)
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 {
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
FROM dmz_category
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
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')
))
)
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 {
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
FROM dmz_category_member
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
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')
))
AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
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 {
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
FROM dmz_category_member
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
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')
))
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')
)
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 {
err = nil

View file

@ -248,6 +248,12 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Sto
SourceType: activity.SourceTypeDocument,
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()
if err != nil {
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
}
// Sort by title.
sort.Sort(doc.ByName(documents))
// Remove documents that cannot be seen due to lack of
// category view/access permission.
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.
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)
}
@ -396,6 +417,14 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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()
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
FROM dmz_doc
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_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
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' AND p.c_refid=? AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
(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'
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' AND p.c_refid=? AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
)
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 {
err = nil
@ -100,6 +98,9 @@ func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
err = errors.Wrap(err, "select documents by space")
}
// (SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
// )
return
}

View file

@ -179,3 +179,19 @@ func (s Store) GetMembers(ctx domain.RequestContext) (r []group.Record, err erro
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 {
files = []attachment.Attachment{}
}
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)

View file

@ -76,7 +76,7 @@ background-color: #f6f6f6;
</tr>
<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">
<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>
</tr>
<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 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">
<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>
</tr>
<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 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">
<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>
</tr>
<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 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">
<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>
</tr>
<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 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">
<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>
</tr>
<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 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">
<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>
</tr>
<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 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">
<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>
</tr>
<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 (
"bytes"
"encoding/base64"
"fmt"
"net/http"
"strings"
@ -57,6 +58,7 @@ func (h *Handler) Meta(w http.ResponseWriter, r *http.Request) {
data.AuthProvider = strings.TrimSpace(org.AuthProvider)
data.AuthConfig = org.AuthConfig
data.MaxTags = org.MaxTags
data.Theme = org.Theme
data.Version = h.Runtime.Product.Version
data.Revision = h.Runtime.Product.Revision
data.Edition = h.Runtime.Product.Edition
@ -111,6 +113,10 @@ Disallow: /auth/*
Disallow: /auth/**
Disallow: /share
Disallow: /share/*
Disallow: /attachments
Disallow: /attachments/*
Disallow: /attachment
Disallow: /attachment/*
Sitemap: %s`, sitemap)
}
@ -190,7 +196,7 @@ func (h *Handler) Reindex(w http.ResponseWriter, r *http.Request) {
if !ctx.GlobalAdmin {
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
}
@ -253,7 +259,7 @@ func (h *Handler) SearchStatus(w http.ResponseWriter, r *http.Request) {
if !ctx.GlobalAdmin {
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
}
@ -277,3 +283,72 @@ type sitemapItem struct {
type searchStatus struct {
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
import (
"bytes"
"database/sql"
"encoding/json"
"github.com/documize/community/model/audit"
"io"
"io/ioutil"
"net/http"
@ -99,3 +102,50 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
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_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
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,
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
WHERE c_refid=?`),
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_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
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,
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
WHERE c_domain=? AND c_active=true`),
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_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
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,
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
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
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`,
&org)
@ -176,3 +176,31 @@ func (s Store) CheckDomain(ctx domain.RequestContext, domain string) string {
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) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
provider.WriteMessage(w, "gemini", "Bad payload")
return
@ -165,7 +164,6 @@ func auth(ctx *provider.Context, store *store.Store, w http.ResponseWriter, r *h
var config = geminiConfig{}
err = json.Unmarshal(body, &config)
if err != nil {
provider.WriteMessage(w, "gemini", "Bad payload")
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")
return
}
if len(config.Username) == 0 {
provider.WriteMessage(w, "gemini", "Missing Username value")
return
}
if len(config.APIKey) == 0 {
provider.WriteMessage(w, "gemini", "Missing APIKey value")
return
}
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.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString(creds))
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
provider.WriteError(w, "gemini", err)
fmt.Println(err)
return
}
if res.StatusCode != http.StatusOK {
provider.WriteForbidden(w)
return
@ -213,9 +207,9 @@ func auth(ctx *provider.Context, store *store.Store, w http.ResponseWriter, r *h
dec := json.NewDecoder(res.Body)
err = dec.Decode(&g)
if err != nil {
provider.WriteError(w, "gemini", err)
fmt.Println(err)
return
}

View file

@ -324,7 +324,7 @@ const renderTemplate = `
<tr>
<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"><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">
{{range $comp := $item.Fields.Components}}

View file

@ -28,7 +28,7 @@ const renderTemplate = `
<tbody>
{{range $item := .Events}}
<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 width-90">{{ $item.Message }}</td>
</tr>

View file

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

View file

@ -30,8 +30,15 @@ type Store struct {
// Add adds new folder into the store.
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 (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Lifecycle, sp.Likes, sp.Created, sp.Revised)
_, 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_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 {
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,
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_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
FROM dmz_space
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,
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_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
FROM dmz_space
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,
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_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
FROM dmz_space
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,
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_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
WHERE c_orgid=? AND (c_type=? OR c_type=?)
UNION ALL
SELECT id, c_refid AS refid,
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_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
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')
@ -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) {
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 {
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) {
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/doc"
"github.com/documize/community/model/group"
"github.com/documize/community/model/label"
"github.com/documize/community/model/link"
"github.com/documize/community/model/org"
"github.com/documize/community/model/page"
@ -42,6 +43,7 @@ type Store struct {
Document DocumentStorer
Group GroupStorer
Link LinkStorer
Label LabelStorer
Meta MetaStorer
Organization OrganizationStorer
Page PageStorer
@ -62,6 +64,10 @@ type SpaceStorer interface {
Update(ctx domain.RequestContext, sp space.Space) (err error)
Delete(ctx domain.RequestContext, id string) (rows int64, 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
@ -148,6 +154,8 @@ type OrganizationStorer interface {
RemoveOrganization(ctx domain.RequestContext, orgID string) (err error)
UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error)
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
@ -285,6 +293,7 @@ type GroupStorer interface {
GetMembers(ctx domain.RequestContext) (r []group.Record, err error)
JoinGroup(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.
@ -295,3 +304,12 @@ type MetaStorer interface {
Attachments(ctx domain.RequestContext, docID string) (a []attachment.Attachment, 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/page"
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"
uuid "github.com/nu7hatch/gouuid"
)
@ -66,23 +66,23 @@ func (h *Handler) SavedList(w http.ResponseWriter, r *http.Request) {
return
}
templates := []template.Template{}
// templates := []template.Template{}
for _, d := range documents {
var t = template.Template{}
t.ID = d.RefID
t.Title = d.Name
t.Description = d.Excerpt
t.Author = ""
t.Dated = d.Created
t.Type = template.TypePrivate
// for _, d := range documents {
// var t = template.Template{}
// t.ID = d.RefID
// t.Title = d.Name
// t.Description = d.Excerpt
// t.Author = ""
// t.Dated = d.Created
// t.Type = template.TypePrivate
if d.SpaceID == spaceID {
templates = append(templates, t)
}
}
// if d.SpaceID == spaceID {
// templates = append(templates, t)
// }
// }
response.WriteJSON(w, templates)
response.WriteJSON(w, documents)
}
// 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
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()
h.Store.Audit.Record(ctx, audit.EventTypeTemplateUse)

View file

@ -405,7 +405,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
// remove all associated roles for this user
// Remove user's permissions
_, err = h.Store.Permission.DeleteUserPermissions(ctx, userID)
if err != nil {
ctx.Transaction.Rollback()
@ -414,6 +414,15 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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()
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.ViewUsers = false
u.Analytics = false
u.Theme = ""
for _, account := range u.Accounts {
if account.OrgID == orgID {
@ -50,6 +51,7 @@ func AttachUserAccounts(ctx domain.RequestContext, s store.Store, orgID string,
u.Active = account.Active
u.ViewUsers = account.Users
u.Analytics = account.Analytics
u.Theme = account.Theme
break
}
}

View file

@ -13,6 +13,7 @@
package boot
import (
"os"
"time"
"github.com/documize/community/core/database"
@ -57,28 +58,37 @@ func InitRuntime(r *env.Runtime, s *store.Store) bool {
storage.SetMySQLProvider(r, s)
case "postgresql":
storage.SetPostgreSQLProvider(r, s)
case "mssql":
// storage.SetSQLServerProvider(r, s)
// case "mssql":
// 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
db, err := sqlx.Open(r.StoreProvider.DriverName(), r.StoreProvider.MakeConnectionString()) //r.Flags.DBConn
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
// Database connection defaults
// Set connection defaults
r.Db.SetMaxIdleConns(30)
r.Db.SetMaxOpenConns(100)
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()
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
}

View file

@ -39,10 +39,10 @@ func main() {
// product details
rt.Product = domain.Product{}
rt.Product.Major = "1"
rt.Product.Minor = "76"
rt.Product.Patch = "2"
rt.Product.Revision = 181121115333
rt.Product.Major = "2"
rt.Product.Minor = "0"
rt.Product.Patch = "0"
rt.Product.Revision = 190115203818
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
rt.Product.Edition = domain.CommunityEdition
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)

View file

@ -27,6 +27,7 @@ import (
category "github.com/documize/community/domain/category"
document "github.com/documize/community/domain/document"
group "github.com/documize/community/domain/group"
label "github.com/documize/community/domain/label"
link "github.com/documize/community/domain/link"
meta "github.com/documize/community/domain/meta"
org "github.com/documize/community/domain/organization"
@ -149,6 +150,11 @@ func SetMySQLProvider(r *env.Runtime, s *store.Store) {
userStore := user.Store{}
userStore.Runtime = r
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.

View file

@ -25,6 +25,7 @@ import (
category "github.com/documize/community/domain/category"
document "github.com/documize/community/domain/document"
group "github.com/documize/community/domain/group"
label "github.com/documize/community/domain/label"
link "github.com/documize/community/domain/link"
meta "github.com/documize/community/domain/meta"
org "github.com/documize/community/domain/organization"
@ -147,6 +148,11 @@ func SetPostgreSQLProvider(r *env.Runtime, s *store.Store) {
userStore := user.Store{}
userStore.Runtime = r
s.User = userStore
// Space Label
labelStore := label.Store{}
labelStore.Runtime = r
s.Label = labelStore
}
// 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
# 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
{
files: [
'testem.js',
'.eslintrc.js',
'.template-lintrc.js',
'ember-cli-build.js',
'testem.js',
'config/**/*.js'
],
parserOptions: {
@ -60,7 +62,6 @@ module.exports = {
"userLogin": true,
"Keycloak": true,
"slug": true,
"interact": true,
"velocity": true
"iziToast": true
}
};

22
gui/.gitignore vendored
View file

@ -1,18 +1,24 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/dist-prod
/tmp
/dist/
/tmp/
/dist-prod/
# dependencies
/node_modules
/bower_components
/bower_components/
/node_modules/
# misc
/.sass-cache
/connect.lock
/coverage/*
/coverage/
/libpeerconnection.log
npm-debug.log*
testem.log
/npm-debug.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
script:
- yarn lint:js
- yarn test
- npm run lint:hbs
- 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 --server`
### Linting
* `npm run lint:hbs`
* `npm run lint:js`
* `npm run lint:js -- --fix`
### Building
* `ember build` (development)

View file

@ -141,15 +141,13 @@ export default Component.extend(ModalMixin, Notifier, {
},
onLDAPPreview() {
this.showWait();
let config = this.get('ldapConfig');
config.serverPort = parseInt(this.get('ldapConfig.serverPort'));
this.get('globalSvc').previewLDAP(config).then((preview) => {
this.set('ldapPreview', preview);
this.modalOpen("#ldap-preview-modal", {"show": true});
this.showDone();
this.notifySuccess('Saved');
});
},
@ -231,8 +229,6 @@ export default Component.extend(ModalMixin, Notifier, {
break;
}
this.showWait();
let data = { authProvider: provider, authConfig: JSON.stringify(config) };
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() {
this.showWait();
this.set('backupFilename', '');
this.set('backupSuccess', false);
this.set('backupFailed', false);
@ -69,13 +68,13 @@ export default Component.extend(Notifier, Modal, {
let spec = this.get('backupSpec');
this.get('onBackup')(spec).then((filename) => {
this.showDone();
this.notifySuccess('Completed');
this.set('backupLabel', 'Start Backup');
this.set('backupSuccess', true);
this.set('backupFilename', filename);
this.set('backupRunning', false);
}, ()=> {
this.showDone();
this.notifyError('Failed');
this.set('backupLabel', 'Run Backup');
this.set('backupFailed', true);
this.set('backupRunning', false);
@ -134,7 +133,6 @@ export default Component.extend(Notifier, Modal, {
}
// start restore process
this.showWait();
this.set('restoreButtonLabel', 'Please wait, restore running...');
this.set('restoreSuccess', false);
this.set('restoreFailed', false);
@ -147,12 +145,12 @@ export default Component.extend(Notifier, Modal, {
}
this.get('onRestore')(spec, filedata).then(() => {
this.showDone();
this.notifySuccess('Completed');
this.set('backupLabel', 'Restore');
this.set('restoreSuccess', true);
this.get('router').transitionTo('auth.logout');
}, ()=> {
this.showDone();
this.notifyError('Failed');
this.set('restorbackupLabel', 'Restore');
this.set('restoreFailed', true);
});

View file

@ -13,10 +13,12 @@ import $ from 'jquery';
import { empty, and } from '@ember/object/computed';
import { isEmpty } from '@ember/utils';
import { set } from '@ember/object';
import { inject as service } from '@ember/service';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component';
export default Component.extend(Notifier, {
appMeta: service(),
maxTags: 3,
titleEmpty: empty('model.general.title'),
messageEmpty: empty('model.general.message'),
@ -30,6 +32,51 @@ export default Component.extend(Notifier, {
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: {
change() {
const selectEl = this.$('#maxTags')[0];
@ -60,16 +107,23 @@ export default Component.extend(Notifier, {
}
this.set('model.general.maxTags', this.get('maxTags'));
this.model.general.set('allowAnonymousAccess', $("#allowAnonymousAccess").prop('checked'));
this.showWait();
this.get('save')().then(() => {
this.showDone();
this.get('onUpdate')().then(() => {
this.notifySuccess('Saved');
set(this, 'titleError', false);
set(this, 'messageError', 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.showWait();
this.get('orgSvc').saveOrgSetting(orgId, 'jira', this.get('jiraCreds')).then(() => {
if (this.get('session.isGlobalAdmin')) {
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: {
saveLicense() {
this.showWait();
this.get('global').setLicense(this.get('license')).then(() => {
this.showDone();
this.notifySuccess('Saved');
window.location.reload();
});
},
@ -57,7 +55,7 @@ export default Component.extend(Notifier, Modals, {
let comment = this.get('comment');
this.get('global').deactivate(comment).then(() => {
this.showDone();
this.notifySuccess('Saved');
this.modalOpen("#deactivation-confirmation-modal", {"show": true});
});
}

View file

@ -10,16 +10,20 @@
// https://documize.com
import { inject as service } from '@ember/service';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component';
export default Component.extend({
export default Component.extend(Notifier, {
appMeta: service(),
buttonLabel: 'Rebuild Search Index',
buttonLabel: 'Rebuild',
actions: {
reindex() {
this.set('buttonLabel', 'Rebuilding search index...')
this.get('reindex')();
this.set('buttonLabel', 'Running...');
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.notifyInfo('Sending test email to you');
this.get('saveSMTP')().then((result) => {
this.showDone();
this.set('buttonText', 'Save & Test');
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() {
this._super(...arguments);
this.loadData();
},
didReceiveAttrs() {
this._super(...arguments);
this.deleteSpace = {
id: '',
name: ''
@ -55,6 +53,7 @@ export default Component.extend(Notifier, Modals, {
actions: {
onShow(id) {
this.set('deleteSpace.id', id);
this.modalOpen("#space-delete-modal", {"show": true}, '#delete-space-name');
},
onDelete() {
@ -76,29 +75,28 @@ export default Component.extend(Notifier, Modals, {
this.set('deleteSpace.id', '');
this.set('deleteSpace.name', '');
this.loadData();
this.notifySuccess('Deleted');
});
},
onExport() {
this.showWait();
let spec = {
spaceId: '',
data: _.pluck(this.get('folders'), 'id'),
filterType: 'space',
};
this.notifyInfo('Export running...');
this.get('documentSvc').export(spec).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, 'documize.html');
this.showDone();
this.notifySuccess('Export completed');
});
},
onOwner(spaceId) {
this.showWait();
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 AuthProvider from '../../mixins/auth';
import ModalMixin from '../../mixins/modal';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component';
export default Component.extend(AuthProvider, ModalMixin, {
export default Component.extend(AuthProvider, ModalMixin, Notifier, {
bulkUsers: '',
newUser: null,
@ -51,6 +52,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
this.get('onAddUser')(user).then(() => {
this.set('newUser', { firstname: '', lastname: '', email: '', active: true });
this.notifySuccess('Added user');
});
this.modalClose("#add-user-modal");
@ -65,6 +67,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
this.get('onAddUsers')(this.get('bulkUsers')).then(() => {
this.set('bulkUsers', '');
this.notifySuccess('Added users');
});
this.modalClose("#add-user-modal");

View file

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

View file

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

View file

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

View file

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

View file

@ -9,176 +9,30 @@
//
// https://documize.com
import $ from 'jquery';
import { A } from '@ember/array';
import { computed } from '@ember/object';
import { notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Modals from '../../mixins/modal';
import Tooltips from '../../mixins/tooltip';
import Component from '@ember/component';
export default Component.extend(Modals, Tooltips, {
export default Component.extend(Modals, {
documentService: service('document'),
sessionService: service('session'),
categoryService: service('category'),
router: service(),
contributorMsg: '',
approverMsg: '',
selectedCategories: A([]),
tagz: A([]),
userChanges: notEmpty('contributorMsg'),
isApprover: computed('permissions', function() {
return this.get('permissions.documentApprove');
}),
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;
unassigned: computed('selectedCategories', 'tagz', function() {
return this.get('selectedCategories').length === 0 && this.get('tagz').length === 0;
}),
didReceiveAttrs() {
this._super(...arguments);
this.workflowStatus();
this.popovers();
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() {
this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => {
this.set('selectedCategories', selected);
@ -198,24 +52,10 @@ export default Component.extend(Modals, Tooltips, {
},
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() {
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 Component from '@ember/component';
import TooltipMixin from '../../mixins/tooltip';
export default Component.extend(TooltipMixin, {
export default Component.extend({
documentService: service('document'),
sectionService: service('section'),
editMode: false,

View file

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

View file

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

View file

@ -125,8 +125,6 @@ export default Component.extend(Notifier, {
actions: {
onSave() {
this.showWait();
let docId = this.get('document.id');
let folderId = this.get('space.id');
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(toLink, 'link').then(() => {
this.showDone();
});
});

View file

@ -32,7 +32,7 @@ export default Component.extend(Notifier, {
if (this.get('hasNameError')) 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());
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';
export default Component.extend(Modals, Notifier, {
classNames: ["section"],
documentService: service('document'),
browserSvc: service('browser'),
appMeta: service(),
session: service(),
hasAttachments: notEmpty('files'),
canEdit: computed('permissions.documentEdit', 'document.protection', function() {
return this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit');
}),
showDialog: false,
init() {
this._super(...arguments);
this.deleteAttachment = { id: '', name: '' };
},
downloadQuery: '',
didReceiveAttrs() {
this._super(...arguments);
@ -47,9 +46,9 @@ export default Component.extend(Modals, Notifier, {
let url = this.get('appMeta.endpoint');
let uploadUrl = `${url}/documents/${documentId}/attachments`;
let dzone = new Dropzone("#upload-document-files", {
let dzone = new Dropzone("#upload-document-files > div", {
headers: {
'Authorization': 'Bearer ' + self.get('session.session.content.authenticated.token')
'Authorization': 'Bearer ' + self.get('session.authToken')
},
url: uploadUrl,
method: "post",
@ -66,17 +65,16 @@ export default Component.extend(Modals, Notifier, {
});
this.on("queuecomplete", function () {
self.showDone();
self.notifySuccess('Uploaded file');
self.getAttachments();
});
this.on("addedfile", function ( /*file*/ ) {
self.showWait();
});
this.on("error", function (error, msg) { // // eslint-disable-line no-unused-vars
self.showNotification(msg);
console.log(msg); // eslint-disable-line no-console
this.on("error", function (error, msg) {
self.notifyError(msg);
self.notifyError(error);
});
}
});
@ -86,6 +84,15 @@ export default Component.extend(Modals, Notifier, {
});
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() {
@ -95,26 +102,20 @@ export default Component.extend(Modals, Notifier, {
},
actions: {
onShowDialog(id, name) {
this.set('deleteAttachment', { id: id, name: name });
this.set('showDialog', true);
onDelete(attachment) {
this.get('documentService').deleteAttachment(this.get('document.id'), attachment.id).then(() => {
this.notifySuccess('File deleted');
this.getAttachments();
});
},
onDelete() {
this.set('showDialog', false);
let attachment = this.get('deleteAttachment');
this.get('documentService').deleteAttachment(this.get('document.id'), attachment.id).then(() => {
this.getAttachments();
this.set('deleteAttachment', {
id: "",
name: ""
});
onExport() {
this.get('documentSvc').export({}).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, this.get('space.slug') + '.html');
this.notifySuccess('Exported');
});
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 { inject as service } from '@ember/service';
import tocUtil from '../../utils/toc';
import TooltipMixin from '../../mixins/tooltip';
import Component from '@ember/component';
export default Component.extend(TooltipMixin, {
export default Component.extend({
classNames: ["section"],
documentService: service('document'),
emptyState: computed('pages', function () {
return this.get('pages.length') === 0;
@ -54,13 +54,11 @@ export default Component.extend(TooltipMixin, {
didInsertElement() {
this._super(...arguments);
this.eventBus.subscribe('documentPageAdded', this, 'onDocumentPageAdded');
this.renderTooltips();
},
willDestroyElement() {
this._super(...arguments);
this.eventBus.unsubscribe('documentPageAdded');
this.removeTooltips();
},
onDocumentPageAdded(pageId) {

View file

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

View file

@ -9,19 +9,15 @@
//
// https://documize.com
import { computed, set } from '@ember/object';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import ModalMixin from '../../mixins/modal';
import Component from '@ember/component';
export default Component.extend(ModalMixin, {
documentService: service('document'),
revision: null,
revisions: null,
diff: '',
hasRevisions: computed('revisions', function() {
return this.get('revisions').length > 0;
}),
revision: null,
hasDiff: computed('diff', function() {
return this.get('diff').length > 0;
}),
@ -34,32 +30,17 @@ export default Component.extend(ModalMixin, {
this.get('document.protection') === constants.ProtectionType.None;
}),
init() {
this._super(...arguments);
this.revisions = [];
},
didReceiveAttrs() {
this._super(...arguments);
this.fetchRevisions();
},
fetchRevisions() {
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);
});
let revision = this.get('revision');
this.set('revisions', revisions);
if (revisions.length > 0 && is.null(this.get('revision'))) {
this.send('onSelectRevision', revisions[0]);
if (is.not.null(revision)) {
if (!revision.deleted) {
this.fetchDiff(revision.pageId, revision.id);
}
});
},
}
},
fetchDiff(pageId, revisionId) {
this.get('documentService').getPageRevisionDiff(this.get('document.id'), pageId, revisionId).then((revision) => {
@ -68,12 +49,8 @@ export default Component.extend(ModalMixin, {
},
actions: {
onSelectRevision(revision) {
this.set('revision', revision);
if (!revision.deleted) {
this.fetchDiff(revision.pageId, revision.id);
}
onShowModal() {
this.modalOpen('#document-rollback-modal', {show:true});
},
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';
export default Component.extend({
classNames: ['hashtags'],
init() {
this._super(...arguments);
let tagz = [];
@ -20,7 +22,7 @@ export default Component.extend({
let tags = this.get('documentTags').split('#');
_.each(tags, function(tag) {
if (tag.length > 0) {
tagz.pushObject("#" + tag);
tagz.pushObject(tag);
}
});
}

View file

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

View file

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

View file

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

View file

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

View file

@ -30,6 +30,8 @@ export default Component.extend(Notifier, Modals, {
searchText: '',
inviteEmail: '',
inviteMessage: '',
showSpacePermExplain: false,
showDocumentPermExplain: false,
isSpaceAdmin: computed('permissions', function() {
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 filteredUsers = A([]);
this.showWait();
this.get('userSvc').matchUsers(s).then((users) => {
users.forEach((user) => {
let exists = spacePermissions.findBy('whoId', user.get('id'));
@ -144,11 +144,30 @@ export default Component.extend(Notifier, Modals, {
});
this.set('filteredUsers', filteredUsers);
this.showDone();
});
},
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() {
this.modalOpen("#space-invite-user-modal", {"show": true}, '#space-invite-email');
},
@ -160,9 +179,7 @@ export default Component.extend(Notifier, Modals, {
onSave() {
if (!this.get('isSpaceAdmin')) return;
this.showWait();
let message = this.getDefaultInvitationMessage();
let message = this.getDefaultInvitationMessage();
let permissions = this.get('spacePermissions');
let folder = this.get('folder');
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.showDone();
this.notifySuccess('Saved');
this.get('onRefresh')();
});
},
@ -219,17 +236,13 @@ export default Component.extend(Notifier, Modals, {
let spacePermissions = this.get('spacePermissions');
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)) {
spacePermissions.pushObject(this.permissionRecord(constants.WhoType.User, user.get('id'), user.get('fullname')));
this.set('spacePermissions', spacePermissions);
this.send('onSearch');
}
this.showDone();
},
onSpaceInvite(e) {
@ -248,9 +261,7 @@ export default Component.extend(Notifier, Modals, {
return;
}
this.showWait();
var result = {
var result = {
Message: message,
Recipients: []
};
@ -271,7 +282,7 @@ export default Component.extend(Notifier, Modals, {
this.set('inviteEmail', '');
this.get('spaceSvc').share(this.get('folder.id'), result).then(() => {
this.showDone();
this.notifySuccess('Invites sent');
this.$('#space-invite-email').removeClass('is-invalid');
this.modalClose("#space-invite-user-modal");
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';
export default Component.extend(AuthMixin, {
classNames: ["section"],
router: service(),
documentService: service('document'),
folderService: service('folder'),
@ -26,6 +27,8 @@ export default Component.extend(AuthMixin, {
spaceSettings: computed('permissions', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
selectedFilter: '',
spaceLabel: null,
init() {
this._super(...arguments);
@ -53,6 +56,7 @@ export default Component.extend(AuthMixin, {
this.set('categories', categories);
this.set('categoryLinkName', categories.length > 0 ? 'Manage' : 'Add');
this.set('spaceLabel', _.findWhere(this.get('labels'), {id: this.get('space.labelId')}));
schedule('afterRender', () => {
if (this.get('categoryFilter') !== '') {
@ -81,8 +85,6 @@ export default Component.extend(AuthMixin, {
});
this.set('categoryFilter', id);
this.set('spaceSelected', false);
this.set('uncategorizedSelected', false);
break;
case 'uncategorized':
@ -94,19 +96,29 @@ export default Component.extend(AuthMixin, {
});
this.set('categoryFilter', '');
this.set('spaceSelected', false);
this.set('uncategorizedSelected', true);
break;
case 'space':
allowed = _.pluck(categoryMembers, 'documentId');
docs.forEach((d) => {
filtered.pushObject(d);
});
this.set('categoryFilter', '');
this.set('spaceSelected', true);
this.set('uncategorizedSelected', false);
break;
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;
}
@ -114,6 +126,7 @@ export default Component.extend(AuthMixin, {
cat.set('selected', cat.get('id') === id);
});
this.set('selectedFilter', filter);
this.set('categories', categories);
this.get('onFiltered')(filtered);
}

View file

@ -13,13 +13,13 @@ import $ from 'jquery';
import { computed } from '@ember/object';
import { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service';
import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal';
import AuthMixin from '../../mixins/auth';
import Notifier from '../../mixins/notifier';
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'),
localStorage: service(),
templateService: service('template'),
@ -75,7 +75,6 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
this.set('pinState.pinId', pinId);
this.set('pinState.isPinned', pinId !== '');
this.set('pinState.newName', folder.get('name'));
this.renderTooltips();
});
let cats = this.get('categories');
@ -92,7 +91,6 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
willDestroyElement() {
this._super(...arguments);
this.removeTooltips();
if (is.not.null(this.get('dropzone'))) {
this.get('dropzone').destroy();
@ -152,11 +150,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
actions: {
onUnpin() {
this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => {
$('#space-pin-button').tooltip('dispose');
this.set('pinState.isPinned', false);
this.set('pinState.pinId', '');
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) => {
$('#space-pin-button').tooltip('dispose');
this.set('pinState.isPinned', true);
this.set('pinState.pinId', pin.get('id'));
this.eventBus.publish('pinChange');
this.renderTooltips();
});
return true;
@ -288,8 +282,6 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
},
onExport() {
this.showWait();
let spec = {
spaceId: this.get('space.id'),
data: [],
@ -310,7 +302,7 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, Notifier, {
this.get('documentSvc').export(spec).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, this.get('space.slug') + '.html');
this.showDone();
this.notifySuccess('Exported');
});
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';
export default Component.extend({
tagName: 'nav',
classNames: ['layout-sidebar', 'non-printable'],
classNameBindings: ['scrollable:sidebar-scroll'],
scrollable: false,
tagName: 'div',
classNames: ['master-container'],
didInsertElement() {
this._super(...arguments);
// let sb = this.$().overlayScrollbars({ scrollbars: { autoHide: 'leave' }});
// this.set('scrollbars', sb);
},
willDestroyElement() {
this._super(...arguments);
// let sb = this.get('scrollbars');
// sb.destroy();
}
});

View file

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

View file

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

View file

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

View file

@ -95,11 +95,14 @@ export default Component.extend({
}
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");
return;
}
$("#stage-2-password").removeClass("is-invalid");
$("#stage-2-password-confirm").removeClass("is-invalid");
self.set('processing', false);
@ -121,7 +124,7 @@ export default Component.extend({
let creds = { password: password, email: user.email };
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);

View file

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

View file

@ -10,10 +10,9 @@
// https://documize.com
import { computed } from '@ember/object';
import TooltipMixin from '../../../mixins/tooltip';
import Component from '@ember/component';
export default Component.extend(TooltipMixin, {
export default Component.extend({
isDirty: false,
pageBody: "",
codeSyntax: null,
@ -100,9 +99,6 @@ export default Component.extend(TooltipMixin, {
editor = null;
this.set('codeEditor', null);
}
this.removeTooltips();
},
// 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 { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
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'),
isDirty: false,
waiting: false,
@ -27,7 +26,7 @@ export default Component.extend(SectionMixin, TooltipMixin, {
this._super(...arguments);
this.user = {};
this.workspaces = [];
this.config = {};
this.config = {};
},
didReceiveAttrs() {
@ -92,7 +91,6 @@ export default Component.extend(SectionMixin, TooltipMixin, {
schedule('afterRender', () => {
window.scrollTo(0, document.body.scrollHeight);
self.renderTooltips();
});
self.set('waiting', false);
}, function (reason) { // eslint-disable-line no-unused-vars

View file

@ -10,11 +10,10 @@
// https://documize.com
import { inject as service } from '@ember/service';
import Component from '@ember/component';
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'),
isDirty: false,
waiting: false,

View file

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

View file

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

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