mirror of
https://github.com/documize/community.git
synced 2025-07-23 15:19:42 +02:00
implemented copy page facility
This commit is contained in:
parent
fc9127e165
commit
07ee2248d4
11 changed files with 269 additions and 18 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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')}}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
{{#each pages key="id" as |page index|}}
|
||||
<div class="wysiwyg">
|
||||
<div id="page-{{ page.id }}" class="is-a-page" data-id="{{ page.id }}" data-type="{{ page.contentType }}">
|
||||
{{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}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
{{#dropdown-menu target=menuTarget position="bottom right" open="click" onOpenCallback=(action 'onMenuOpen') onCloseCallback=(action 'onMenuOpen')}}
|
||||
<ul class="menu">
|
||||
<li class="item">Duplicate</li>
|
||||
<li class="item" id={{copyButtonId}}>Copy</li>
|
||||
<li class="item">Move</li>
|
||||
<li class="item" id="saveas-page-button-{{page.id}}">Publish</li>
|
||||
<li class="divider"></li>
|
||||
|
@ -23,14 +23,14 @@
|
|||
{{/dropdown-menu}}
|
||||
|
||||
{{#if menuOpen}}
|
||||
{{#dropdown-dialog target=deleteButtonId position="top right" button="Delete" color="flat-red" onAction=(action 'deletePage' page.id)}}
|
||||
{{#dropdown-dialog target=deleteButtonId position="bottom right" button="Delete" color="flat-red" onAction=(action 'deletePage' page.id)}}
|
||||
<p>Are you sure you want to delete <span class="bold">{{page.title}}?</span></p>
|
||||
<p>
|
||||
{{input type="checkbox" id=checkId class="margin-left-20" checked=deleteChildren}}
|
||||
<label for="{{checkId}}"> Delete child pages</label>
|
||||
</p>
|
||||
{{/dropdown-dialog}}
|
||||
{{#dropdown-dialog id=saveAsDialogId target=saveAsTarget position="top right" button="Publish" color="flat-green" focusOn=blockTitleId onAction=(action 'onAddBlock' page)}}
|
||||
{{#dropdown-dialog id=saveAsDialogId target=saveAsTarget position="bottom right" button="Publish" color="flat-green" focusOn=blockTitleId onAction=(action 'onAddBlock' page)}}
|
||||
<div class="form-header">
|
||||
<div class="tip">
|
||||
<span class="bold">{{folder.name}}:</span> Content Block
|
||||
|
@ -46,6 +46,18 @@
|
|||
<div class="tip">Short description to help others understand<br/>the reusable content block</div>
|
||||
{{textarea rows="3" value=blockExcerpt id=blockExcerptId}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
{{#dropdown-dialog id=copyDialogId target=copyButtonId position="bottom right" button="Copy" color="flat-green" onOpenCallback=(action 'onCopyDialogOpen') onAction=(action 'onCopyPage' page)}}
|
||||
<div class="form-header">
|
||||
<div class="tip">
|
||||
<span class="bold">Copy:</span> {{page.title}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-control">
|
||||
<label>Target</label>
|
||||
<div class="tip">Select where the content should be copied to</div>
|
||||
{{ui-select content=documentList action=(action 'onTargetChange') optionValuePath="id" optionLabelPath="name" selection=document}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</ul>
|
||||
{{#if hasBlocks}}
|
||||
<div class="divider"></div>
|
||||
<div class="template-caption">Reusable Content</div>
|
||||
<div class="template-caption">Reusable content</div>
|
||||
<ul class="list">
|
||||
{{#each blocks as |block|}}
|
||||
<li class="item">
|
||||
|
@ -54,7 +54,7 @@
|
|||
</ul>
|
||||
{{else}}
|
||||
<div class="divider"></div>
|
||||
<div class="template-caption">Published, reusable sections appear below</div>
|
||||
<div class="template-caption">Reusable content appears below</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue