From 07ee2248d41eb5d4f78589f3fc598c22211471bf Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Sun, 22 Jan 2017 14:12:10 -0800 Subject: [PATCH] implemented copy page facility --- app/app/components/document/document-view.js | 4 + app/app/components/document/page-heading.js | 39 +++++ app/app/pods/document/index/controller.js | 12 ++ app/app/pods/document/index/template.hbs | 2 +- app/app/services/document.js | 31 +++- .../components/document/document-view.hbs | 3 +- .../components/document/page-heading.hbs | 18 ++- .../components/document/page-wizard.hbs | 4 +- core/api/endpoint/page_endpoint.go | 134 +++++++++++++++++- core/api/endpoint/router.go | 15 +- core/api/request/document.go | 25 ++++ 11 files changed, 269 insertions(+), 18 deletions(-) diff --git a/app/app/components/document/document-view.js b/app/app/components/document/document-view.js index a0656c7b..59a528d8 100644 --- a/app/app/components/document/document-view.js +++ b/app/app/components/document/document-view.js @@ -74,6 +74,10 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, { this.attrs.onAddBlock(block); }, + onCopyPage(pageId, documentId) { + this.attrs.onCopyPage(pageId, documentId); + }, + onDeletePage(id, deleteChildren) { let page = this.get('pages').findBy("id", id); diff --git a/app/app/components/document/page-heading.js b/app/app/components/document/page-heading.js index 8e1ee5ce..cd2898a1 100644 --- a/app/app/components/document/page-heading.js +++ b/app/app/components/document/page-heading.js @@ -23,6 +23,9 @@ export default Ember.Component.extend(TooltipMixin, { menuOpen: false, blockTitle: "", blockExcerpt: "", + documentList: [], //includes the current document + documentListOthers: [], //excludes the current document + selectedDocument: null, checkId: computed('page', function () { let id = this.get('page.id'); @@ -52,6 +55,14 @@ export default Ember.Component.extend(TooltipMixin, { let id = this.get('page.id'); return `block-excerpt-${id}`; }), + copyButtonId: computed('page', function () { + let id = this.get('page.id'); + return `copy-page-button-${id}`; + }), + copyDialogId: computed('page', function () { + let id = this.get('page.id'); + return `copy-dialog-${id}`; + }), didRender() { if (this.get('isEditor')) { @@ -125,5 +136,33 @@ export default Ember.Component.extend(TooltipMixin, { return true; }); }, + + // Copy action + onCopyDialogOpen() { + // Fetch document targets once. + if (this.get('documentList').length > 0) { + return; + } + + this.get('documentService').getPageMoveCopyTargets().then((d) => { + let me = this.get('document'); + this.set('documentList', d); + this.set('documentListOthers', d.filter((item) => item.get('id') !== me.get('id'))); + }); + }, + + onTargetChange(d) { + this.set('selectedDocument', d); + }, + + onCopyPage(page) { + let targetDocumentId = this.get('document.id'); + if (is.not.null(this.get('selectedDocument'))) { + targetDocumentId = this.get('selectedDocument.id') + } + + this.attrs.onCopyPage(page.get('id'), targetDocumentId); + return true; + } } }); diff --git a/app/app/pods/document/index/controller.js b/app/app/pods/document/index/controller.js index 716531d9..4c6fd658 100644 --- a/app/app/pods/document/index/controller.js +++ b/app/app/pods/document/index/controller.js @@ -92,6 +92,18 @@ export default Ember.Controller.extend(NotifierMixin, { }); }, + onCopyPage(pageId, targetDocumentId) { + let documentId = this.get('model.document.id'); + this.get('documentService').copyPage(documentId, pageId, targetDocumentId).then((page) => { + this.showNotification("Copied"); + + // refresh data if copied to same document + if (documentId === targetDocumentId) { + this.get('target.router').refresh(); + } + }); + }, + onPageDeleted(deletePage) { let documentId = this.get('model.document.id'); let pages = this.get('model.pages'); diff --git a/app/app/pods/document/index/template.hbs b/app/app/pods/document/index/template.hbs index 28a9a265..09c8b792 100644 --- a/app/app/pods/document/index/template.hbs +++ b/app/app/pods/document/index/template.hbs @@ -1,2 +1,2 @@ {{document/document-view document=model.document links=model.links allPages=model.allPages tabs=model.tabs pages=model.pages folder=model.folder folders=model.folders isEditor=model.isEditor - gotoPage=(action 'gotoPage') onAddBlock=(action 'onAddBlock') onDeletePage=(action 'onPageDeleted')}} + gotoPage=(action 'gotoPage') onAddBlock=(action 'onAddBlock') onCopyPage=(action 'onCopyPage') onDeletePage=(action 'onPageDeleted')}} diff --git a/app/app/services/document.js b/app/app/services/document.js index 53499202..98f0abb8 100644 --- a/app/app/services/document.js +++ b/app/app/services/document.js @@ -292,10 +292,39 @@ export default Ember.Service.extend({ // nuke an attachment deleteAttachment(documentId, attachmentId) { - return this.get('ajax').request(`documents/${documentId}/attachments/${attachmentId}`, { method: 'DELETE' }); + }, + + //************************************************** + // Page Move Copy + //************************************************** + + // Return list of documents that can accept a page. + getPageMoveCopyTargets() { + return this.get('ajax').request(`sections/targets`, { + method: 'GET' + }).then((response) => { + let data = []; + + data = response.map((obj) => { + let data = this.get('store').normalize('document', obj); + return this.get('store').push(data); + }); + + return data; + }); + }, + + // Copy existing page to same or different document. + copyPage(documentId, pageId, targetDocumentId) { + return this.get('ajax').request(`documents/${documentId}/pages/${pageId}/copy/${targetDocumentId}`, { + method: 'POST' + }).then((response) => { + let data = this.get('store').normalize('page', response); + return this.get('store').push(data); + }); } }); diff --git a/app/app/templates/components/document/document-view.hbs b/app/app/templates/components/document/document-view.hbs index 51e70a2b..79721305 100644 --- a/app/app/templates/components/document/document-view.hbs +++ b/app/app/templates/components/document/document-view.hbs @@ -17,7 +17,8 @@ {{#each pages key="id" as |page index|}}
- {{document/page-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor onAddBlock=(action 'onAddBlock') onDeletePage=(action 'onDeletePage')}} + {{document/page-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor + onAddBlock=(action 'onAddBlock') onCopyPage=(action 'onCopyPage') onDeletePage=(action 'onDeletePage')}} {{section/base-renderer page=page}}
diff --git a/app/app/templates/components/document/page-heading.hbs b/app/app/templates/components/document/page-heading.hbs index 66b2c637..b7563c40 100644 --- a/app/app/templates/components/document/page-heading.hbs +++ b/app/app/templates/components/document/page-heading.hbs @@ -14,7 +14,7 @@ {{#dropdown-menu target=menuTarget position="bottom right" open="click" onOpenCallback=(action 'onMenuOpen') onCloseCallback=(action 'onMenuOpen')}} {{#if hasBlocks}}
-
Reusable Content
+
Reusable content
{{else}}
-
Published, reusable sections appear below
+
Reusable content appears below
{{/if}} diff --git a/core/api/endpoint/page_endpoint.go b/core/api/endpoint/page_endpoint.go index 73d80fd1..1deb6a79 100644 --- a/core/api/endpoint/page_endpoint.go +++ b/core/api/endpoint/page_endpoint.go @@ -669,9 +669,38 @@ func GetDocumentPageMeta(w http.ResponseWriter, r *http.Request) { writeSuccessBytes(w, json) } -/******************** -* Page Revisions -********************/ +// GetPageMoveCopyTargets returns available documents for page copy/move axction. +func GetPageMoveCopyTargets(w http.ResponseWriter, r *http.Request) { + method := "GetPageMoveCopyTargets" + p := request.GetPersister(r) + + var d []entity.Document + var err error + + d, err = p.GetDocumentList() + + if len(d) == 0 { + d = []entity.Document{} + } + + if err != nil { + writeGeneralSQLError(w, method, err) + return + } + + json, err := json.Marshal(d) + + if err != nil { + writeJSONMarshalError(w, method, "document", err) + return + } + + writeSuccessBytes(w, json) +} + +//************************************************** +// Page Revisions +//************************************************** // GetDocumentRevisions returns all changes for a document. func GetDocumentRevisions(w http.ResponseWriter, r *http.Request) { @@ -897,3 +926,102 @@ func RollbackDocumentPage(w http.ResponseWriter, r *http.Request) { writeSuccessBytes(w, payload) } + +//************************************************** +// Copy Move Page +//************************************************** + +// CopyPage copies page to either same or different document. +func CopyPage(w http.ResponseWriter, r *http.Request) { + method := "CopyPage" + p := request.GetPersister(r) + + params := mux.Vars(r) + documentID := params["documentID"] + pageID := params["pageID"] + targetID := params["targetID"] + + // data checks + if len(documentID) == 0 { + writeMissingDataError(w, method, "documentID") + return + } + if len(pageID) == 0 { + writeMissingDataError(w, method, "pageID") + return + } + if len(targetID) == 0 { + writeMissingDataError(w, method, "targetID") + return + } + + // permission + if !p.CanViewDocument(documentID) { + writeForbiddenError(w) + return + } + + // fetch data + page, err := p.GetPage(pageID) + if err == sql.ErrNoRows { + writeNotFoundError(w, method, documentID) + return + } + if err != nil { + writeGeneralSQLError(w, method, err) + return + } + + pageMeta, err := p.GetPageMeta(pageID) + if err == sql.ErrNoRows { + writeNotFoundError(w, method, documentID) + return + } + if err != nil { + writeGeneralSQLError(w, method, err) + return + } + + newPageID := util.UniqueID() + page.RefID = newPageID + page.Level = 1 + page.DocumentID = targetID + page.UserID = p.Context.UserID + pageMeta.DocumentID = targetID + pageMeta.PageID = newPageID + pageMeta.UserID = p.Context.UserID + + model := new(models.PageModel) + model.Meta = pageMeta + model.Page = page + + tx, err := request.Db.Beginx() + if err != nil { + writeTransactionError(w, method, err) + return + } + p.Context.Transaction = tx + + err = p.AddPage(*model) + if err != nil { + log.IfErr(tx.Rollback()) + writeGeneralSQLError(w, method, err) + return + } + + if len(model.Page.BlockID) > 0 { + p.IncrementBlockUsage(model.Page.BlockID) + } + + log.IfErr(tx.Commit()) + + newPage, _ := p.GetPage(pageID) + json, err := json.Marshal(newPage) + + if err != nil { + writeJSONMarshalError(w, method, "page", err) + return + } + + writeSuccessBytes(w, json) +} diff --git a/core/api/endpoint/router.go b/core/api/endpoint/router.go index 2fa20d16..cb756b34 100644 --- a/core/api/endpoint/router.go +++ b/core/api/endpoint/router.go @@ -127,8 +127,9 @@ func buildRoutes(prefix string) *mux.Router { } func init() { - - // **** add Unsecure Routes + //************************************************** + // Non-secure routes + //************************************************** log.IfErr(Add(RoutePrefixPublic, "meta", []string{"GET", "OPTIONS"}, nil, GetMeta)) log.IfErr(Add(RoutePrefixPublic, "authenticate", []string{"POST", "OPTIONS"}, nil, Authenticate)) @@ -139,7 +140,9 @@ func init() { log.IfErr(Add(RoutePrefixPublic, "attachments/{orgID}/{attachmentID}", []string{"GET", "OPTIONS"}, nil, AttachmentDownload)) log.IfErr(Add(RoutePrefixPublic, "version", []string{"GET", "OPTIONS"}, nil, version)) - // **** add secure routes + //************************************************** + // Secure routes + //************************************************** // Import & Convert Document log.IfErr(Add(RoutePrefixPrivate, "import/folder/{folderID}", []string{"POST", "OPTIONS"}, nil, UploadConvertDocument)) @@ -151,8 +154,6 @@ func init() { log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, GetDocument)) log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, UpdateDocument)) log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"DELETE", "OPTIONS"}, nil, DeleteDocument)) - - // Document Meta log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/meta", []string{"GET", "OPTIONS"}, nil, GetDocumentMeta)) // Document Page @@ -173,9 +174,8 @@ func init() { log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/attachments", []string{"GET", "OPTIONS"}, nil, GetAttachments)) log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/attachments/{attachmentID}", []string{"DELETE", "OPTIONS"}, nil, DeleteAttachment)) log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/attachments", []string{"POST", "OPTIONS"}, nil, AddAttachments)) - - // Document Page Meta log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/meta", []string{"GET", "OPTIONS"}, nil, GetDocumentPageMeta)) + log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/copy/{targetID}", []string{"POST", "OPTIONS"}, nil, CopyPage)) // Organization log.IfErr(Add(RoutePrefixPrivate, "organizations/{orgID}", []string{"GET", "OPTIONS"}, nil, GetOrganization)) @@ -221,6 +221,7 @@ func init() { log.IfErr(Add(RoutePrefixPrivate, "sections/blocks/{blockID}", []string{"PUT", "OPTIONS"}, nil, UpdateBlock)) log.IfErr(Add(RoutePrefixPrivate, "sections/blocks/{blockID}", []string{"DELETE", "OPTIONS"}, nil, DeleteBlock)) log.IfErr(Add(RoutePrefixPrivate, "sections/blocks", []string{"POST", "OPTIONS"}, nil, AddBlock)) + log.IfErr(Add(RoutePrefixPrivate, "sections/targets", []string{"GET", "OPTIONS"}, nil, GetPageMoveCopyTargets)) // Links log.IfErr(Add(RoutePrefixPrivate, "links/{folderID}/{documentID}/{pageID}", []string{"GET", "OPTIONS"}, nil, GetLinkCandidates)) diff --git a/core/api/request/document.go b/core/api/request/document.go index 2167c49e..8ca40cb3 100644 --- a/core/api/request/document.go +++ b/core/api/request/document.go @@ -212,6 +212,31 @@ AND d.template=0`, orgID) return } +// GetDocumentList returns a slice containing the documents available as templates to the client's organisation, in title order. +func (p *Persister) GetDocumentList() (documents []entity.Document, err error) { + err = Db.Select(&documents, + `SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=0 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))) + ORDER BY title`, + p.Context.OrgID, + p.Context.OrgID, + p.Context.UserID, + p.Context.OrgID, + p.Context.OrgID, + p.Context.OrgID, + p.Context.OrgID, + p.Context.UserID) + + if err != nil { + log.Error(fmt.Sprintf("Unable to execute GetDocumentList org %s", p.Context.OrgID), err) + return + } + + return +} + // SearchDocument searches the documents that the client is allowed to see, using the keywords search string, then audits that search. // Visible documents include both those in the client's own organisation and those that are public, or whose visibility includes the client. func (p *Persister) SearchDocument(keywords string) (results []entity.DocumentSearch, err error) {