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

Enable custom logo upload and rendering

This commit is contained in:
McMatts 2019-01-06 13:50:12 +00:00
parent a211ba051a
commit 036f36ba1d
36 changed files with 574 additions and 211 deletions

View file

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

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

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

View file

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

View file

@ -13,6 +13,7 @@ package meta
import ( import (
"bytes" "bytes"
"encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -301,3 +302,41 @@ func (h *Handler) Themes(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, th) 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)
logo, err := h.Store.Organization.Logo(ctx, d)
if err != nil {
h.Runtime.Log.Infof("unable to fetch logo for domain %s", d)
response.WriteNotFound(w)
return
}
if len(string(logo)) == 0 {
logo, err = base64.StdEncoding.DecodeString(defaultLogo)
if err != nil {
h.Runtime.Log.Error("unable to decode default logo", err)
response.WriteEmpty(w)
return
}
}
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 org logo", err)
return
}
}
var defaultLogo = `
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
`

View file

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

View file

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

View file

@ -99,6 +99,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
var sp space.Space var sp space.Space
sp.Name = model.Name sp.Name = model.Name
sp.Description = model.Description
sp.Icon = model.Icon
sp.LabelID = model.LabelID
sp.RefID = uniqueid.Generate() sp.RefID = uniqueid.Generate()
sp.OrgID = ctx.OrgID sp.OrgID = ctx.OrgID
sp.UserID = ctx.UserID sp.UserID = ctx.UserID

View file

@ -30,7 +30,12 @@ type Store struct {
// Add adds new folder into the store. // Add adds new folder into the store.
func (s Store) Add(ctx domain.RequestContext, sp space.Space) (err error) { func (s Store) Add(ctx domain.RequestContext, sp space.Space) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_space (c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle, c_likes, c_icon, c_desc, c_count_category, c_count_content, c_labelid, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"), _, 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.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Lifecycle, sp.Likes,
sp.Icon, sp.Description, sp.CountCategory, sp.CountContent, sp.LabelID, sp.Icon, sp.Description, sp.CountCategory, sp.CountContent, sp.LabelID,
sp.Created, sp.Revised) sp.Created, sp.Revised)

View file

@ -154,6 +154,8 @@ type OrganizationStorer interface {
RemoveOrganization(ctx domain.RequestContext, orgID string) (err error) RemoveOrganization(ctx domain.RequestContext, orgID string) (err error)
UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error)
CheckDomain(ctx domain.RequestContext, domain string) string CheckDomain(ctx domain.RequestContext, domain string) string
Logo(ctx domain.RequestContext, domain string) (l []byte, err error)
UploadLogo(ctx domain.RequestContext, l []byte) (err error)
} }
// PinStorer defines required methods for pin management // PinStorer defines required methods for pin management

View file

@ -32,6 +32,51 @@ export default Component.extend(Notifier, {
this.set('maxTags', this.get('model.general.maxTags')); this.set('maxTags', this.get('model.general.maxTags'));
}, },
didInsertElement() {
this._super(...arguments);
let self = this;
let url = this.get('appMeta.endpoint');
let orgId = this.get('appMeta.orgId');
let uploadUrl = `${url}/organization/${orgId}/logo`;
let dzone = new Dropzone("#upload-logo > div", {
headers: {
'Authorization': 'Bearer ' + self.get('session.authToken')
},
url: uploadUrl,
method: "post",
paramName: 'attachment',
clickable: true,
maxFilesize: 50,
parallelUploads: 1,
uploadMultiple: false,
addRemoveLinks: false,
autoProcessQueue: true,
createImageThumbnails: false,
init: function () {
this.on("success", function (/*file, response*/ ) {
});
this.on("queuecomplete", function () {
self.notifySuccess('Logo uploaded');
});
this.on("error", function (error, msg) {
self.notifyError(msg);
self.notifyError(error);
});
}
});
dzone.on("complete", function (file) {
dzone.removeFile(file);
});
this.set('drop', dzone);
},
actions: { actions: {
change() { change() {
const selectEl = this.$('#maxTags')[0]; const selectEl = this.$('#maxTags')[0];
@ -63,7 +108,7 @@ export default Component.extend(Notifier, {
this.set('model.general.maxTags', this.get('maxTags')); this.set('model.general.maxTags', this.get('maxTags'));
this.get('save')().then(() => { this.get('onUpdate')().then(() => {
this.notifySuccess('Saved'); this.notifySuccess('Saved');
set(this, 'titleError', false); set(this, 'titleError', false);
set(this, 'messageError', false); set(this, 'messageError', false);
@ -74,6 +119,11 @@ export default Component.extend(Notifier, {
onThemeChange(theme) { onThemeChange(theme) {
this.get('appMeta').setTheme(theme); this.get('appMeta').setTheme(theme);
this.set('model.general.theme', theme); this.set('model.general.theme', theme);
},
onDefaultLogo() {
this.get('onDefaultLogo')(this.get('appMeta.orgId'));
this.notifySuccess('Using default logo');
} }
} }
}); });

View file

@ -20,6 +20,7 @@ import Component from '@ember/component';
export default Component.extend(AuthMixin, Notifier, { export default Component.extend(AuthMixin, Notifier, {
router: service(), router: service(),
spaceSvc: service('folder'), spaceSvc: service('folder'),
iconSvc: service('icon'),
localStorage: service('localStorage'), localStorage: service('localStorage'),
isSpaceAdmin: computed('permissions', function() { isSpaceAdmin: computed('permissions', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
@ -40,7 +41,7 @@ export default Component.extend(AuthMixin, Notifier, {
init() { init() {
this._super(...arguments); this._super(...arguments);
this.populateIconList(); this.set('iconList', this.get('iconSvc').getSpaceIconList());
}, },
didReceiveAttrs() { didReceiveAttrs() {
@ -76,62 +77,6 @@ export default Component.extend(AuthMixin, Notifier, {
this.set('spaceIcon', icon); this.set('spaceIcon', icon);
}, },
populateIconList() {
let list = this.get('iconList');
let constants = this.get('constants');
list = A([]);
list.pushObject(constants.IconMeta.Star);
list.pushObject(constants.IconMeta.Support);
list.pushObject(constants.IconMeta.Message);
list.pushObject(constants.IconMeta.Apps);
list.pushObject(constants.IconMeta.Box);
list.pushObject(constants.IconMeta.Gift);
list.pushObject(constants.IconMeta.Design);
list.pushObject(constants.IconMeta.Bulb);
list.pushObject(constants.IconMeta.Metrics);
list.pushObject(constants.IconMeta.PieChart);
list.pushObject(constants.IconMeta.BarChart);
list.pushObject(constants.IconMeta.Finance);
list.pushObject(constants.IconMeta.Lab);
list.pushObject(constants.IconMeta.Code);
list.pushObject(constants.IconMeta.Help);
list.pushObject(constants.IconMeta.Manuals);
list.pushObject(constants.IconMeta.Flow);
list.pushObject(constants.IconMeta.Out);
list.pushObject(constants.IconMeta.In);
list.pushObject(constants.IconMeta.Partner);
list.pushObject(constants.IconMeta.Org);
list.pushObject(constants.IconMeta.Home);
list.pushObject(constants.IconMeta.Infinite);
list.pushObject(constants.IconMeta.Todo);
list.pushObject(constants.IconMeta.Procedure);
list.pushObject(constants.IconMeta.Outgoing);
list.pushObject(constants.IconMeta.Incoming);
list.pushObject(constants.IconMeta.Travel);
list.pushObject(constants.IconMeta.Winner);
list.pushObject(constants.IconMeta.Roadmap);
list.pushObject(constants.IconMeta.Money);
list.pushObject(constants.IconMeta.Security);
list.pushObject(constants.IconMeta.Tune);
list.pushObject(constants.IconMeta.Guide);
list.pushObject(constants.IconMeta.Smile);
list.pushObject(constants.IconMeta.Rocket);
list.pushObject(constants.IconMeta.Time);
list.pushObject(constants.IconMeta.Cup);
list.pushObject(constants.IconMeta.Marketing);
list.pushObject(constants.IconMeta.Announce);
list.pushObject(constants.IconMeta.Devops);
list.pushObject(constants.IconMeta.World);
list.pushObject(constants.IconMeta.Plan);
list.pushObject(constants.IconMeta.Components);
list.pushObject(constants.IconMeta.People);
list.pushObject(constants.IconMeta.Checklist);
this.set('iconList', list);
},
actions: { actions: {
onSetSpaceType(t) { onSetSpaceType(t) {
this.set('spaceType', t); this.set('spaceType', t);

View file

@ -9,9 +9,20 @@
// //
// https://documize.com // https://documize.com
import { inject as service } from '@ember/service';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend({
appMeta: service(),
icon: null, icon: null,
meta: null meta: null,
logo: false,
didReceiveAttrs() {
this._super(...arguments);
if (this.get('logo')) {
let cb = + new Date();
this.set('cacheBuster', cb);
}
}
}); });

View file

@ -0,0 +1,19 @@
// 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 stringUtil from '../utils/string';
import { helper } from '@ember/component/helper';
// Usage: {{random-id}}
export default helper(function() {
return stringUtil.makeId(10);
});

View file

@ -1,8 +1,9 @@
<div class="auth-center"> <div class="auth-center">
<div class="auth-box"> <div class="auth-box">
<div class="logo"> <div class="logo">
<img src="/assets/img/logo-purple.png" title="Documize" alt="Documize" class="img-fluid"> <img src="{{appMeta.endpoint}}/public/logo?cb={{random-id}}"
<div class="url">{{appMeta.appHost}}</div> title="Documize" alt="Documize" class="img-fluid">
<div class="url">{{appMeta.title}} ({{appMeta.appHost}})</div>
</div> </div>
<div class="login-form"> <div class="login-form">
{{user/forgot-password forgot=(action "forgot")}} {{user/forgot-password forgot=(action "forgot")}}

View file

@ -2,8 +2,9 @@
{{#if model.showLogin}} {{#if model.showLogin}}
<div class="auth-box"> <div class="auth-box">
<div class="logo"> <div class="logo">
<img src="/assets/img/logo-purple.png" title="Documize" alt="Documize" class="img-fluid"> <img src="{{appMeta.endpoint}}/public/logo?cb={{random-id}}"
<div class="url">{{appMeta.appHost}}</div> title="Documize" alt="Documize" class="img-fluid">
<div class="url">{{appMeta.title}} ({{appMeta.appHost}})</div>
</div> </div>
<form {{action "login" on="submit"}}> <form {{action "login" on="submit"}}>
<div class="form-group"> <div class="form-group">

View file

@ -1,8 +1,9 @@
<div class="auth-center"> <div class="auth-center">
<div class="auth-box"> <div class="auth-box">
<div class="logo"> <div class="logo">
<img src="/assets/img/logo-purple.png" title="Documize" alt="Documize" class="img-fluid"> <img src="{{appMeta.endpoint}}/public/logo?cb={{random-id}}"
<div class="url">{{appMeta.appHost}}</div> title="Documize" alt="Documize" class="img-fluid">
<div class="url">{{appMeta.title}} ({{appMeta.appHost}})</div>
</div> </div>
{{user/password-reset reset=(action "reset")}} {{user/password-reset reset=(action "reset")}}
</div> </div>

View file

@ -16,9 +16,13 @@ export default Controller.extend({
orgService: service('organization'), orgService: service('organization'),
actions: { actions: {
save() { onUpdate() {
return this.get('orgService').save(this.model.general).then(() => { return this.get('orgService').save(this.model.general).then(() => {
}); });
},
onDefaultLogo(orgId) {
return this.get('orgService').useDefaultLogo(orgId);
} }
} }
}); });

View file

@ -3,4 +3,6 @@
desc="Options to help you customize Documize" desc="Options to help you customize Documize"
icon=constants.Icon.Settings}} icon=constants.Icon.Settings}}
{{customize/general-settings model=model save=(action "save")}} {{customize/general-settings model=model
onUpdate=(action "onUpdate")
onDefaultLogo=(action "onDefaultLogo")}}

View file

@ -19,19 +19,20 @@ import Controller from '@ember/controller';
export default Controller.extend(AuthMixin, Modals, { export default Controller.extend(AuthMixin, Modals, {
appMeta: service(), appMeta: service(),
folderService: service('folder'), folderService: service('folder'),
spaceName: '',
copyTemplate: true, copyTemplate: true,
copyPermission: true, copyPermission: true,
copyDocument: false, copyDocument: false,
hasClone: notEmpty('clonedSpace.id'), hasClone: notEmpty('clonedSpace.id'),
clonedSpace: null, clonedSpace: null,
selectedView: 'all', selectedView: 'all',
selectedSpaces: null, selectedSpaces: null,
publicSpaces: null, publicSpaces: null,
protectedSpaces: null, protectedSpaces: null,
personalSpaces: null, personalSpaces: null,
spaceIcon: '',
spaceLabel: '',
spaceDesc: '',
spaceName: '',
actions: { actions: {
onShowModal() { onShowModal() {
@ -42,10 +43,22 @@ export default Controller.extend(AuthMixin, Modals, {
this.set('clonedSpace', sp) this.set('clonedSpace', sp)
}, },
onSetIcon(icon) {
this.set('spaceIcon', icon);
},
onSetLabel(id) {
this.set('spaceLabel', id);
},
onAddSpace(e) { onAddSpace(e) {
e.preventDefault(); e.preventDefault();
let spaceName = this.get('spaceName'); let spaceName = this.get('spaceName');
let spaceDesc = this.get('spaceDesc');
let spaceIcon = this.get('spaceIcon');
let spaceLabel = this.get('spaceLabel');
let clonedId = this.get('clonedSpace.id'); let clonedId = this.get('clonedSpace.id');
if (is.empty(spaceName)) { if (is.empty(spaceName)) {
@ -55,6 +68,9 @@ export default Controller.extend(AuthMixin, Modals, {
let payload = { let payload = {
name: spaceName, name: spaceName,
desc: spaceDesc,
icon: spaceIcon,
labelId: spaceLabel,
cloneId: clonedId, cloneId: clonedId,
copyTemplate: this.get('copyTemplate'), copyTemplate: this.get('copyTemplate'),
copyPermission: this.get('copyPermission'), copyPermission: this.get('copyPermission'),
@ -62,6 +78,9 @@ export default Controller.extend(AuthMixin, Modals, {
} }
this.set('spaceName', ''); this.set('spaceName', '');
this.set('spaceDesc', '');
this.set('spaceIcon', '');
this.set('spaceLabel', '');
this.set('clonedSpace', null); this.set('clonedSpace', null);
$("#new-space-name").removeClass("is-invalid"); $("#new-space-name").removeClass("is-invalid");

View file

@ -17,6 +17,7 @@ import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-rout
export default Route.extend(AuthenticatedRouteMixin, { export default Route.extend(AuthenticatedRouteMixin, {
appMeta: service(), appMeta: service(),
folderService: service('folder'), folderService: service('folder'),
iconSvc: service('icon'),
localStorage: service(), localStorage: service(),
labelSvc: service('label'), labelSvc: service('label'),
@ -66,6 +67,7 @@ export default Route.extend(AuthenticatedRouteMixin, {
controller.set('publicSpaces', publicSpaces); controller.set('publicSpaces', publicSpaces);
controller.set('protectedSpaces', protectedSpaces); controller.set('protectedSpaces', protectedSpaces);
controller.set('personalSpaces', personalSpaces); controller.set('personalSpaces', personalSpaces);
controller.set('iconList', this.get('iconSvc').getSpaceIconList());
}, },
activate() { activate() {

View file

@ -32,11 +32,13 @@
{{#if labels}} {{#if labels}}
<div class="list"> <div class="list">
{{#each labels as |label|}} {{#each labels as |label|}}
{{#if (gt label.count 0)}}
<div class="item {{if (eq selectedView label.id) "selected"}}" {{action "onSelect" label.id}}> <div class="item {{if (eq selectedView label.id) "selected"}}" {{action "onSelect" label.id}}>
<i class={{concat "dicon " constants.Icon.Checkbox}} <i class={{concat "dicon " constants.Icon.Checkbox}}
style={{label.bgfgColor}}/> style={{label.bgfgColor}}/>
<div class="name">{{label.name}} ({{label.count}})</div> <div class="name">{{label.name}} ({{label.count}})</div>
</div> </div>
{{/if}}
{{/each}} {{/each}}
</div> </div>
{{else}} {{else}}
@ -48,8 +50,10 @@
{{#layout/master-content}} {{#layout/master-content}}
<div class="grid-container-8-2"> <div class="grid-container-8-2">
<div class="grid-cell-1"> <div class="grid-cell-1">
{{layout/page-heading title=appMeta.title}} {{layout/logo-heading
{{layout/page-desc desc=appMeta.message}} title=appMeta.title
desc=appMeta.message
logo=true}}
</div> </div>
<div class="grid-cell-2 grid-cell-right"> <div class="grid-cell-2 grid-cell-right">
{{#if (or session.isEditor session.isAdmin)}} {{#if (or session.isEditor session.isAdmin)}}
@ -68,16 +72,49 @@
{{spaces/space-list spaces=selectedSpaces labels=labels}} {{spaces/space-list spaces=selectedSpaces labels=labels}}
<div class="modal" tabindex="-1" role="dialog" id="add-space-modal"> <div class="modal" tabindex="-1" role="dialog" id="add-space-modal">
<div class="modal-dialog" role="document"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header">Add Space</div> <div class="modal-header">New Space</div>
<div class="modal-body"> <div class="modal-body">
<form onsubmit={{action "onAddSpace"}}> <form onsubmit={{action "onAddSpace"}}>
<div class="form-group"> <div class="form-group">
<label for="new-space-name">Space Name</label> <label for="new-space-name">Name</label>
{{input type="text" id="new-space-name" class="form-control mousetrap" placeholder="Space name" value=spaceName}} {{input type="text" id="new-space-name" class="form-control mousetrap" placeholder="Space name" value=spaceName}}
<small class="form-text text-muted">Characters and numbers only</small> <small class="form-text text-muted">Characters and numbers only</small>
</div> </div>
<div class="form-group">
<label>Description</label>
{{focus-input id="space-desc" type="text" value=spaceDesc class="form-control" placeholder="Space description" autocomplete="off"}}
</div>
<div class="form-group">
<label>Icon</label>
<div class="ui-icon-picker">
<ul class="list">
{{#each iconList as |icon|}}
<li class="item {{if (eq spaceIcon icon) "selected"}}" {{action "onSetIcon" icon}}>
{{ui/ui-icon-meta icon=icon}}
</li>
{{/each}}
</ul>
</div>
</div>
<div class="form-group">
<label>Label</label>
<ul class="space-label-picker">
<li class="label none {{if (eq spaceLabel "") "selected"}}" {{action "onSetLabel" ""}}>None</li>
{{#each labels as |label|}}
<li class="label {{if (eq spaceLabel label.id) "selected"}}"
style={{label.bgColor}}
{{action "onSetLabel" label.id}} title={{label.name}}>
{{label.name}}
</li>
{{/each}}
</ul>
</div>
<div class="form-group"> <div class="form-group">
<label for="clone-space-dropdown">Clone Space</label> <label for="clone-space-dropdown">Clone Space</label>
{{ui/ui-select id="clone-space-dropdown" content=model prompt="select space" action=(action "onCloneSpaceSelect") optionValuePath="id" optionLabelPath="name" selection=clonedSpace}} {{ui/ui-select id="clone-space-dropdown" content=model prompt="select space" action=(action "onCloneSpaceSelect") optionValuePath="id" optionLabelPath="name" selection=clonedSpace}}

69
gui/app/services/icon.js Normal file
View file

@ -0,0 +1,69 @@
// 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 { A } from '@ember/array';
import Service from '@ember/service';
export default Service.extend({
getSpaceIconList() {
let cs = this.get('constants');
let list = A([]);
list.pushObject(cs.IconMeta.Star);
list.pushObject(cs.IconMeta.Support);
list.pushObject(cs.IconMeta.Message);
list.pushObject(cs.IconMeta.Apps);
list.pushObject(cs.IconMeta.Box);
list.pushObject(cs.IconMeta.Gift);
list.pushObject(cs.IconMeta.Design);
list.pushObject(cs.IconMeta.Bulb);
list.pushObject(cs.IconMeta.Metrics);
list.pushObject(cs.IconMeta.PieChart);
list.pushObject(cs.IconMeta.BarChart);
list.pushObject(cs.IconMeta.Finance);
list.pushObject(cs.IconMeta.Lab);
list.pushObject(cs.IconMeta.Code);
list.pushObject(cs.IconMeta.Help);
list.pushObject(cs.IconMeta.Manuals);
list.pushObject(cs.IconMeta.Flow);
list.pushObject(cs.IconMeta.Out);
list.pushObject(cs.IconMeta.In);
list.pushObject(cs.IconMeta.Partner);
list.pushObject(cs.IconMeta.Org);
list.pushObject(cs.IconMeta.Home);
list.pushObject(cs.IconMeta.Infinite);
list.pushObject(cs.IconMeta.Todo);
list.pushObject(cs.IconMeta.Procedure);
list.pushObject(cs.IconMeta.Outgoing);
list.pushObject(cs.IconMeta.Incoming);
list.pushObject(cs.IconMeta.Travel);
list.pushObject(cs.IconMeta.Winner);
list.pushObject(cs.IconMeta.Roadmap);
list.pushObject(cs.IconMeta.Money);
list.pushObject(cs.IconMeta.Security);
list.pushObject(cs.IconMeta.Tune);
list.pushObject(cs.IconMeta.Guide);
list.pushObject(cs.IconMeta.Smile);
list.pushObject(cs.IconMeta.Rocket);
list.pushObject(cs.IconMeta.Time);
list.pushObject(cs.IconMeta.Cup);
list.pushObject(cs.IconMeta.Marketing);
list.pushObject(cs.IconMeta.Announce);
list.pushObject(cs.IconMeta.Devops);
list.pushObject(cs.IconMeta.World);
list.pushObject(cs.IconMeta.Plan);
list.pushObject(cs.IconMeta.Components);
list.pushObject(cs.IconMeta.People);
list.pushObject(cs.IconMeta.Checklist);
return list;
}
});

View file

@ -72,5 +72,12 @@ export default Service.extend({
method: 'POST', method: 'POST',
data: JSON.stringify(config) data: JSON.stringify(config)
}); });
},
useDefaultLogo(orgId) {
return this.get('ajax').request(`organization/${orgId}/logo`, {
method: 'POST',
data: '',
});
} }
}); });

View file

@ -15,7 +15,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
> .icon, > .meta-icon { > .icon, > .meta-icon, > .meta-logo {
align-self: center; align-self: center;
margin-right: 25px; margin-right: 25px;

View file

@ -339,4 +339,10 @@
color: $color-white; color: $color-white;
} }
} }
> #upload-logo {
.dz-preview, .dz-processing, .dz-file-preview {
display: none !important;
}
}
} }

View file

@ -254,7 +254,7 @@
@extend .text-truncate; @extend .text-truncate;
display: inline-block; display: inline-block;
width: 200px; width: 200px;
margin: 0 20px 20px 0; margin: 0 10px 10px 0;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
font-size: 1rem; font-size: 1rem;
font-weight: 500; font-weight: 500;

View file

@ -22,13 +22,13 @@
justify-self: self-start; justify-self: self-start;
> .name { > .name {
font-size: 1.3rem; font-size: 1.4rem;
font-weight: 700; font-weight: 700;
color: map-get($gray-shades, 800); color: map-get($gray-shades, 800);
> .icon { > .icon {
color: map-get($gray-shades, 700); color: map-get($gray-shades, 700);
font-size: 20px; font-size: 26px;
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
margin-right: 10px; margin-right: 10px;

View file

@ -1,5 +1,4 @@
<div class="view-customize"> <div class="view-customize">
<form>
<div class="form-group"> <div class="form-group">
<label for="siteTitle">Site Name</label> <label for="siteTitle">Site Name</label>
{{focus-input id="siteTitle" type="text" value=model.general.title class=(if hasTitleInputError "form-control is-invalid" "form-control")}} {{focus-input id="siteTitle" type="text" value=model.general.title class=(if hasTitleInputError "form-control is-invalid" "form-control")}}
@ -13,18 +12,28 @@
<div class="form-group"> <div class="form-group">
<label>Site Theme</label> <label>Site Theme</label>
{{ui/theme-picker onChange=(action "onThemeChange")}} {{ui/theme-picker onChange=(action "onThemeChange")}}
<small class="form-text text-muted">Users can set their own theme under Profile</small>
</div> </div>
<div class="form-group">
<label>Site Logo</label>
<div>
{{ui/ui-button light=true color=constants.Color.Gray label="Use Default" onClick=(action "onDefaultLogo")}}
{{ui/ui-button-gap}}
{{ui/ui-button light=true color=constants.Color.Yellow label="Upload Custom" id="upload-logo"}}
</div>
<small class="form-text text-muted">You can choose to upload a small logo (e.g. 64px x 64px)</small>
</div>
<div class="form-group"> <div class="form-group">
<label>Public Spaces Viewable By Anonymous Users</label> <label>Public Spaces Viewable By Anonymous Users</label>
{{x-toggle value=model.general.allowAnonymousAccess size="medium" theme="light" onToggle=(action (mut model.general.allowAnonymousAccess))}} {{x-toggle value=model.general.allowAnonymousAccess size="medium" theme="light" onToggle=(action (mut model.general.allowAnonymousAccess))}}
<small class="form-text text-muted">Share content with unauthenticated site visitors</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="conversionEndpoint">Conversion Service URL</label> <label for="conversionEndpoint">Conversion Service URL</label>
{{input id="conversionEndpoint" type="text" value=model.general.conversionEndpoint class=(if hasConversionEndpointInputError "form-control is-invalid" "form-control")}} {{input id="conversionEndpoint" type="text" value=model.general.conversionEndpoint class=(if hasConversionEndpointInputError "form-control is-invalid" "form-control")}}
<small class="form-text text-muted"> <small class="form-text text-muted">
Endpoint for handling import/export (e.g. https://api.documize.com, Endpoint for handling import/export (e.g. https://api.documize.com,
<a href="https://docs.documize.com/s/WNEpptWJ9AABRnha/administration-guides/d/WO0pt_MXigAB6sJ7/general-options">view documentation</a>) <a href="https://docs.documize.com/s/WNEpptWJ9AABRnha/administration-guides/d/WO0pt_MXigAB6sJ7/general-options">read the documentation</a>)
</small> </small>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -43,5 +52,4 @@
</div> </div>
{{ui/ui-button color=constants.Color.Green light=true icon=constants.Icon.Settings label=constants.Label.Save onClick=(action "save")}} {{ui/ui-button color=constants.Color.Green light=true icon=constants.Icon.Settings label=constants.Label.Save onClick=(action "save")}}
</form>
</div> </div>

View file

@ -10,7 +10,7 @@
<ul class="space-labels"> <ul class="space-labels">
{{#each labels as |label|}} {{#each labels as |label|}}
<li class="label" style={{concat "background-color:" label.color ";"}}> <li class="label" style={{label.bgColor}}>
<div class="grid-container-6-4"> <div class="grid-container-6-4">
<div class="grid-cell-1 grid-cell-middle"> <div class="grid-cell-1 grid-cell-middle">
{{label.name}} {{label.name}}

View file

@ -38,7 +38,7 @@
<li class="label none {{if (eq spaceLabel "") "selected"}}" {{action "onSetLabel" ""}}>None</li> <li class="label none {{if (eq spaceLabel "") "selected"}}" {{action "onSetLabel" ""}}>None</li>
{{#each labels as |label|}} {{#each labels as |label|}}
<li class="label {{if (eq spaceLabel label.id) "selected"}}" <li class="label {{if (eq spaceLabel label.id) "selected"}}"
style={{concat "background-color:" label.color ";"}} style={{label.bgColor}}
{{action "onSetLabel" label.id}} title={{label.name}}> {{action "onSetLabel" label.id}} title={{label.name}}>
{{label.name}} {{label.name}}
</li> </li>

View file

@ -7,6 +7,11 @@
<div class="meta-icon"> <div class="meta-icon">
{{ui/ui-icon-meta icon=meta}} {{ui/ui-icon-meta icon=meta}}
</div> </div>
{{else if logo}}
<div class="meta-logo">
<img src="{{appMeta.endpoint}}/public/logo?cb={{cacheBuster}}"
style="max-width: 200px; max-height: 100px;">
</div>
{{/if}} {{/if}}
<div class="text"> <div class="text">
{{layout/page-heading title=title}} {{layout/page-heading title=title}}

View file

@ -99,6 +99,7 @@ const (
EventTypeLabelAdd EventType = "added-label" EventTypeLabelAdd EventType = "added-label"
EventTypeLabelUpdate EventType = "updated-label" EventTypeLabelUpdate EventType = "updated-label"
EventTypeLabelDelete EventType = "removed-label" EventTypeLabelDelete EventType = "removed-label"
EventTypeOrganizationLogo EventType = "uploaded-logo"
// EventTypeVersionAdd records addition of version // EventTypeVersionAdd records addition of version
EventTypeVersionAdd EventType = "added-version" EventTypeVersionAdd EventType = "added-version"

View file

@ -44,5 +44,4 @@ type SiteMeta struct {
Storage env.StoreType `json:"storageProvider"` Storage env.StoreType `json:"storageProvider"`
Location string `json:"location"` // reserved for internal use Location string `json:"location"` // reserved for internal use
Theme string `json:"theme"` // default side-wide theme, user overrideble Theme string `json:"theme"` // default side-wide theme, user overrideble
Logo []byte `json:"logo"`
} }

View file

@ -94,6 +94,9 @@ type InvitationModel struct {
// NewSpaceRequest details the new space to create. // NewSpaceRequest details the new space to create.
type NewSpaceRequest struct { type NewSpaceRequest struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"desc"`
LabelID string `json:"labelId"`
Icon string `json:"icon"`
CloneID string `json:"cloneId"` // existing space to clone, empty = no cloning CloneID string `json:"cloneId"` // existing space to clone, empty = no cloning
CopyTemplate bool `json:"copyTemplate"` // copy templates and reusable content blocks CopyTemplate bool `json:"copyTemplate"` // copy templates and reusable content blocks
CopyPermission bool `json:"copyPermission"` // copy uer permissions CopyPermission bool `json:"copyPermission"` // copy uer permissions

View file

@ -95,6 +95,7 @@ func RegisterEndpoints(rt *env.Runtime, s *store.Store) {
AddPublic(rt, "reset/{token}", []string{"POST", "OPTIONS"}, nil, user.ResetPassword) AddPublic(rt, "reset/{token}", []string{"POST", "OPTIONS"}, nil, user.ResetPassword)
AddPublic(rt, "share/{spaceID}", []string{"POST", "OPTIONS"}, nil, space.AcceptInvitation) AddPublic(rt, "share/{spaceID}", []string{"POST", "OPTIONS"}, nil, space.AcceptInvitation)
AddPublic(rt, "attachment/{orgID}/{attachmentID}", []string{"GET", "OPTIONS"}, nil, attachment.Download) AddPublic(rt, "attachment/{orgID}/{attachmentID}", []string{"GET", "OPTIONS"}, nil, attachment.Download)
AddPublic(rt, "logo", []string{"GET", "OPTIONS"}, nil, meta.Logo)
//************************************************** //**************************************************
// Secured private routes (require authentication) // Secured private routes (require authentication)
@ -131,6 +132,7 @@ func RegisterEndpoints(rt *env.Runtime, s *store.Store) {
AddPrivate(rt, "organization/{orgID}", []string{"PUT", "OPTIONS"}, nil, organization.Update) AddPrivate(rt, "organization/{orgID}", []string{"PUT", "OPTIONS"}, nil, organization.Update)
AddPrivate(rt, "organization/{orgID}/setting", []string{"GET", "OPTIONS"}, nil, setting.GetInstanceSetting) AddPrivate(rt, "organization/{orgID}/setting", []string{"GET", "OPTIONS"}, nil, setting.GetInstanceSetting)
AddPrivate(rt, "organization/{orgID}/setting", []string{"POST", "OPTIONS"}, nil, setting.SaveInstanceSetting) AddPrivate(rt, "organization/{orgID}/setting", []string{"POST", "OPTIONS"}, nil, setting.SaveInstanceSetting)
AddPrivate(rt, "organization/{orgID}/logo", []string{"POST", "OPTIONS"}, nil, organization.UploadLogo)
AddPrivate(rt, "space/{spaceID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete) AddPrivate(rt, "space/{spaceID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete)
AddPrivate(rt, "space/{spaceID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove) AddPrivate(rt, "space/{spaceID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove)