diff --git a/domain/document/mysql/store.go b/domain/document/mysql/store.go index b49c2cf7..b004c9da 100644 --- a/domain/document/mysql/store.go +++ b/domain/document/mysql/store.go @@ -12,6 +12,7 @@ package mysql import ( + "database/sql" "fmt" "time" @@ -199,6 +200,11 @@ func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu ctx.OrgID, ctx.UserID) + if err == sql.ErrNoRows { + err = nil + documents = []doc.Document{} + } + if err != nil { err = errors.Wrap(err, "select space document templates") return @@ -241,6 +247,11 @@ func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document ctx.OrgID, ctx.UserID) + if err == sql.ErrNoRows { + err = nil + documents = []doc.Document{} + } + if err != nil { err = errors.Wrap(err, "select documents list") return diff --git a/domain/link/mysql/store.go b/domain/link/mysql/store.go index 88d4b08b..85a327e8 100644 --- a/domain/link/mysql/store.go +++ b/domain/link/mysql/store.go @@ -13,6 +13,7 @@ package mysql import ( "fmt" + "strings" "time" "github.com/documize/community/core/env" @@ -162,15 +163,18 @@ func (s Scope) DeleteLink(ctx domain.RequestContext, id string) (rows int64, err // SearchCandidates returns matching documents, sections and attachments using keywords. func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (docs []link.Candidate, pages []link.Candidate, attachments []link.Candidate, err error) { + // find matching documents temp := []link.Candidate{} - likeQuery := "title LIKE '%" + keywords + "%'" + keywords = strings.TrimSpace(strings.ToLower(keywords)) + likeQuery := "LOWER(title) LIKE '%" + keywords + "%'" err = s.Runtime.Db.Select(&temp, - `SELECT refid as documentid, labelid as folderid,title from document WHERE orgid=? AND `+likeQuery+` AND labelid IN - (SELECT refid from label WHERE orgid=? AND type=2 AND userid=? - UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) - UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) + `SELECT d.refid as documentid, d. labelid as folderid, d.title, l.label as context + FROM document d LEFT JOIN label l ON d.labelid=l.refid WHERE l.orgid=? AND `+likeQuery+` AND d.labelid IN + (SELECT refid FROM label WHERE orgid=? AND type=2 AND userid=? + UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=1 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) + UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=3 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) ORDER BY title`, ctx.OrgID, ctx.OrgID, @@ -194,22 +198,22 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc TargetID: r.DocumentID, LinkType: "document", Title: r.Title, - Context: "", + Context: r.Context, } docs = append(docs, c) } // find matching sections - likeQuery = "p.title LIKE '%" + keywords + "%'" + likeQuery = "LOWER(p.title) LIKE '%" + keywords + "%'" temp = []link.Candidate{} err = s.Runtime.Db.Select(&temp, - `SELECT p.refid as targetid, p.documentid as documentid, p.title as title, p.pagetype as linktype, d.title as context, d.labelid as folderid from page p - LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+` AND d.labelid IN - (SELECT refid from label WHERE orgid=? AND type=2 AND userid=? - UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) - UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) + `SELECT p.refid as targetid, p.documentid as documentid, p.title as title, p.pagetype as linktype, d.title as context, d.labelid as folderid + FROM page p LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+` AND d.labelid IN + (SELECT refid FROM label WHERE orgid=? AND type=2 AND userid=? + UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=1 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) + UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=3 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) ORDER BY p.title`, ctx.OrgID, ctx.OrgID, @@ -240,15 +244,15 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc } // find matching attachments - likeQuery = "a.filename LIKE '%" + keywords + "%'" + likeQuery = "LOWER(a.filename) LIKE '%" + keywords + "%'" temp = []link.Candidate{} err = s.Runtime.Db.Select(&temp, - `SELECT a.refid as targetid, a.documentid as documentid, a.filename as title, a.extension as context, d.labelid as folderid from attachment a - LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+` AND d.labelid IN - (SELECT refid from label WHERE orgid=? AND type=2 AND userid=? - UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) - UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) + `SELECT a.refid as targetid, a.documentid as documentid, a.filename as title, a.extension as context, d.labelid as folderid + FROM attachment a LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+` AND d.labelid IN + (SELECT refid FROM label WHERE orgid=? AND type=2 AND userid=? + UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=1 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) + UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=3 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) ORDER BY a.filename`, ctx.OrgID, ctx.OrgID, diff --git a/domain/space/endpoint.go b/domain/space/endpoint.go index 006ca0a5..b6e7d239 100644 --- a/domain/space/endpoint.go +++ b/domain/space/endpoint.go @@ -32,8 +32,11 @@ import ( "github.com/documize/community/domain/mail" "github.com/documize/community/model/account" "github.com/documize/community/model/audit" + "github.com/documize/community/model/doc" + "github.com/documize/community/model/page" "github.com/documize/community/model/space" "github.com/documize/community/model/user" + uuid "github.com/nu7hatch/gouuid" ) // Handler contains the runtime information such as logging and database. @@ -65,15 +68,17 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { return } - var sp = space.Space{} - err = json.Unmarshal(body, &sp) + var model = space.NewSpaceRequest{} + err = json.Unmarshal(body, &model) if err != nil { response.WriteServerError(w, method, err) h.Runtime.Log.Error(method, err) return } - if len(sp.Name) == 0 { + model.Name = strings.TrimSpace(model.Name) + model.CloneID = strings.TrimSpace(model.CloneID) + if len(model.Name) == 0 { response.WriteMissingDataError(w, method, "name") return } @@ -85,6 +90,8 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { return } + var sp space.Space + sp.Name = model.Name sp.RefID = uniqueid.Generate() sp.OrgID = ctx.OrgID sp.Type = space.ScopePrivate @@ -118,8 +125,135 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { h.Store.Audit.Record(ctx, audit.EventTypeSpaceAdd) + // Get back new space sp, _ = h.Store.Space.Get(ctx, sp.RefID) + fmt.Println(model) + + // clone existing space? + if model.CloneID != "" && (model.CopyDocument || model.CopyPermission || model.CopyTemplate) { + ctx.Transaction, err = h.Runtime.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + + spCloneRoles, err := h.Store.Space.GetRoles(ctx, model.CloneID) + if err != nil { + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + + if model.CopyPermission { + for _, r := range spCloneRoles { + r.RefID = uniqueid.Generate() + r.LabelID = sp.RefID + + err = h.Store.Space.AddRole(ctx, r) + if err != nil { + ctx.Transaction.Rollback() + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + } + } + + toCopy := []doc.Document{} + spCloneTemplates, err := h.Store.Document.TemplatesBySpace(ctx, model.CloneID) + if err != nil { + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + toCopy = append(toCopy, spCloneTemplates...) + + if model.CopyDocument { + docs, err := h.Store.Document.GetBySpace(ctx, model.CloneID) + + if err != nil { + ctx.Transaction.Rollback() + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + + toCopy = append(toCopy, docs...) + } + + if len(toCopy) > 0 { + for _, t := range toCopy { + origID := t.RefID + + documentID := uniqueid.Generate() + t.RefID = documentID + t.LabelID = sp.RefID + + err = h.Store.Document.Add(ctx, t) + if err != nil { + ctx.Transaction.Rollback() + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + + pages, _ := h.Store.Page.GetPages(ctx, origID) + for _, p := range pages { + meta, err2 := h.Store.Page.GetPageMeta(ctx, p.RefID) + if err2 != nil { + ctx.Transaction.Rollback() + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + + p.DocumentID = documentID + pageID := uniqueid.Generate() + p.RefID = pageID + + meta.PageID = pageID + meta.DocumentID = documentID + + model := page.NewPage{} + model.Page = p + model.Meta = meta + + err = h.Store.Page.Add(ctx, model) + + if err != nil { + ctx.Transaction.Rollback() + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + } + + newUUID, _ := uuid.NewV4() + attachments, _ := h.Store.Attachment.GetAttachmentsWithData(ctx, origID) + for _, a := range attachments { + attachmentID := uniqueid.Generate() + a.RefID = attachmentID + a.DocumentID = documentID + a.Job = newUUID.String() + random := secrets.GenerateSalt() + a.FileID = random[0:9] + + err = h.Store.Attachment.Add(ctx, a) + if err != nil { + ctx.Transaction.Rollback() + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + } + } + } + + ctx.Transaction.Commit() + } + response.WriteJSON(w, sp) } diff --git a/gui/app/components/folder/sidebar-folders-list.js b/gui/app/components/folder/sidebar-folders-list.js index 61afe8a1..97b916ec 100644 --- a/gui/app/components/folder/sidebar-folders-list.js +++ b/gui/app/components/folder/sidebar-folders-list.js @@ -24,9 +24,10 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, { hasPrivateFolders: false, newFolder: '', copyTemplate: true, - copyBlock: true, copyPermission: true, copyDocument: false, + clonedSpace: { id: "" }, + showSpace: false, didReceiveAttrs() { let folders = this.get('folders'); @@ -60,17 +61,42 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, { }, actions: { - addFolder() { - var folderName = this.get('newFolder'); + onToggleNewSpace() { + let val = !this.get('showSpace'); + this.set('showSpace', val); + + if (val) { + Ember.run.schedule('afterRender', () => { + $("#new-folder-name").focus(); + }); + } + }, + + onCloneSpaceSelect(sp) { + this.set('clonedSpace', sp) + }, + + onAdd() { + let folderName = this.get('newFolder'); + let clonedId = this.get('clonedSpace.id'); if (is.empty(folderName)) { $("#new-folder-name").addClass("error").focus(); return false; } - this.attrs.onFolderAdd(folderName); + let payload = { + name: folderName, + CloneID: clonedId, + copyTemplate: this.get('copyTemplate'), + copyPermission: this.get('copyPermission'), + copyDocument: this.get('copyDocument'), + } + this.attrs.onAddSpace(payload); + this.set('showSpace', false); this.set('newFolder', ''); + return true; } } diff --git a/gui/app/components/folder/sidebar-zone.js b/gui/app/components/folder/sidebar-zone.js index bd5f40cd..8337a6d4 100644 --- a/gui/app/components/folder/sidebar-zone.js +++ b/gui/app/components/folder/sidebar-zone.js @@ -51,13 +51,13 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, { let folder = this.get('folder'); this.set('pinState.pinId', this.get('pinned').isSpacePinned(folder.get('id'))); this.set('pinState.isPinned', this.get('pinState.pinId') !== ''); - this.set('pinState.newName', folder.get('name').substring(0,3).toUpperCase()); + this.set('pinState.newName', folder.get('name').substring(0,3).toUpperCase()); } }, actions: { - onFolderAdd(folderName) { - this.attrs.onFolderAdd(folderName); + onAddSpace(m) { + this.attrs.onAddSpace(m); return true; }, diff --git a/gui/app/pods/folder/controller.js b/gui/app/pods/folder/controller.js index 09d01750..84788b7d 100644 --- a/gui/app/pods/folder/controller.js +++ b/gui/app/pods/folder/controller.js @@ -62,11 +62,11 @@ export default Ember.Controller.extend(NotifierMixin, { }); }, - onFolderAdd(folder) { + onAddSpace(payload) { let self = this; this.showNotification("Added"); - this.get('folderService').add({ name: folder }).then(function (newFolder) { + this.get('folderService').add(payload).then(function (newFolder) { self.get('folderService').setCurrentFolder(newFolder); self.transitionToRoute('folder', newFolder.get('id'), newFolder.get('slug')); }); diff --git a/gui/app/pods/folder/template.hbs b/gui/app/pods/folder/template.hbs index eaaac033..6707783f 100644 --- a/gui/app/pods/folder/template.hbs +++ b/gui/app/pods/folder/template.hbs @@ -2,7 +2,7 @@ {{#layout/zone-container}} {{#layout/zone-sidebar}} {{folder/sidebar-zone folders=model.folders folder=model.folder isFolderOwner=model.isFolderOwner isEditor=model.isEditor tab=tab - onFolderAdd=(action 'onFolderAdd')}} + onAddSpace=(action 'onAddSpace')}} {{/layout/zone-sidebar}} {{#layout/zone-content}} {{folder/folder-heading folder=model.folder isFolderOwner=model.isFolderOwner isEditor=model.isEditor}} diff --git a/gui/app/pods/folders/controller.js b/gui/app/pods/folders/controller.js index 54afc4c3..db687c06 100644 --- a/gui/app/pods/folders/controller.js +++ b/gui/app/pods/folders/controller.js @@ -16,11 +16,11 @@ export default Ember.Controller.extend(NotifierMixin, { folderService: Ember.inject.service('folder'), actions: { - onFolderAdd(folder) { + onAddSpace(m) { let self = this; this.showNotification("Added"); - this.get('folderService').add({ name: folder }).then(function (newFolder) { + this.get('folderService').add(m).then(function (newFolder) { self.get('folderService').setCurrentFolder(newFolder); self.transitionToRoute('folder', newFolder.get('id'), newFolder.get('slug')); }); diff --git a/gui/app/pods/folders/template.hbs b/gui/app/pods/folders/template.hbs index b9af815e..f15d8e93 100644 --- a/gui/app/pods/folders/template.hbs +++ b/gui/app/pods/folders/template.hbs @@ -2,7 +2,7 @@ {{#layout/zone-container}} {{#layout/zone-sidebar}} {{folder/sidebar-zone folders=model noFolder=true isFolderOwner=false isEditor=false - onFolderAdd=(action 'onFolderAdd')}} + onAddSpace=(action 'onAddSpace')}} {{/layout/zone-sidebar}} {{#layout/zone-content}} {{/layout/zone-content}} diff --git a/gui/app/services/folder.js b/gui/app/services/folder.js index 5a6d2b01..195a1000 100644 --- a/gui/app/services/folder.js +++ b/gui/app/services/folder.js @@ -29,10 +29,10 @@ export default BaseService.extend({ canEditCurrentFolder: false, // Add a new folder. - add(folder) { + add(payload) { return this.get('ajax').post(`folders`, { contentType: 'json', - data: JSON.stringify(folder) + data: JSON.stringify(payload) }).then((folder) => { let data = this.get('store').normalize('folder', folder); return this.get('store').push(data); diff --git a/gui/app/styles/view/document/content-linker.scss b/gui/app/styles/view/document/content-linker.scss index 21434a45..c9b68ec8 100644 --- a/gui/app/styles/view/document/content-linker.scss +++ b/gui/app/styles/view/document/content-linker.scss @@ -5,7 +5,7 @@ .content-linker-dialog { width: 350px; - height: 350px; + height: 450px; overflow-y: auto; .link-list { diff --git a/gui/app/styles/widget/widget-checkbox.scss b/gui/app/styles/widget/widget-checkbox.scss index 1088f7c8..82b5ab5c 100644 --- a/gui/app/styles/widget/widget-checkbox.scss +++ b/gui/app/styles/widget/widget-checkbox.scss @@ -7,10 +7,9 @@ margin: 0 0 5px 0; > .material-icons { - font-size: 1.4rem; + font-size: 1rem; color: $color-gray; vertical-align: top; - margin-right: 5px; } > .selected { @@ -20,6 +19,13 @@ &:hover { color: $color-link; } + + > .text { + display: inline-block; + font-size: 0.9rem; + vertical-align: text-top; + color: $color-off-black; + } } .ui-checkbox-selected { diff --git a/gui/app/styles/widget/widget-input.scss b/gui/app/styles/widget/widget-input.scss index 0233b1b1..6834351b 100644 --- a/gui/app/styles/widget/widget-input.scss +++ b/gui/app/styles/widget/widget-input.scss @@ -189,6 +189,7 @@ pointer-events: none; font-weight: bold; color: $color-off-black; + @extend .no-select; } > .tip { @@ -198,6 +199,7 @@ padding: 0; font-family: $font-light; text-align: left; + @extend .no-select; } } diff --git a/gui/app/templates/components/document/content-linker.hbs b/gui/app/templates/components/document/content-linker.hbs index 25af8aa6..06644e6c 100644 --- a/gui/app/templates/components/document/content-linker.hbs +++ b/gui/app/templates/components/document/content-linker.hbs @@ -43,7 +43,7 @@ {{#each matches.documents as |m|}}
Copy existing space
- {{#ui/ui-checkbox selected=copyTemplate}}Templates{{/ui/ui-checkbox}} - {{#ui/ui-checkbox selected=copyBlock}}Reusable content{{/ui/ui-checkbox}} - {{#ui/ui-checkbox selected=copyPermission}}Permissions{{/ui/ui-checkbox}} - {{#ui/ui-checkbox selected=copyDocument}}Documents{{/ui/ui-checkbox}} + {{#unless showSpace}} +Optionally, clone existing space
+ {{ui-select id="owners-dropdown" content=folders prompt="select space" action=(action 'onCloneSpaceSelect') optionValuePath="id" optionLabelPath="name" selection=cloneSpace}} + + {{#ui/ui-checkbox selected=copyTemplate}}Templates{{/ui/ui-checkbox}} + {{#ui/ui-checkbox selected=copyPermission}}Permissions{{/ui/ui-checkbox}} + {{#ui/ui-checkbox selected=copyDocument}}Documents{{/ui/ui-checkbox}}- {{#each publicFolders as |folder|}} - {{#link-to 'folder' folder.id folder.slug class="link" activeClass='selected' }} -- {{ folder.name }}
- {{/link-to}}
- {{/each}}
-
-- {{#each protectedFolders as |folder|}} - {{#link-to 'folder' folder.id folder.slug class="link" activeClass='selected' }} -- {{ folder.name }}
- {{/link-to}}
- {{/each}}
-
-- {{#each privateFolders as |folder|}} - {{#link-to 'folder' folder.id folder.slug class="link" activeClass='selected' }} -- {{ folder.name }}
- {{/link-to}}
- {{/each}}
-
-+ {{#each publicFolders as |folder|}} + {{#link-to 'folder' folder.id folder.slug class="link" activeClass='selected' }} +- {{ folder.name }}
+ {{/link-to}}
+ {{/each}}
+
++ {{#each protectedFolders as |folder|}} + {{#link-to 'folder' folder.id folder.slug class="link" activeClass='selected' }} +- {{ folder.name }}
+ {{/link-to}}
+ {{/each}}
+
++ {{#each privateFolders as |folder|}} + {{#link-to 'folder' folder.id folder.slug class="link" activeClass='selected' }} +- {{ folder.name }}
+ {{/link-to}}
+ {{/each}}
+
+