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

reusable content block editing completed

This commit is contained in:
Harvey Kandola 2017-01-21 17:28:31 -08:00
parent bbc2237ef7
commit 2493a09ba9
15 changed files with 307 additions and 23 deletions

View file

@ -0,0 +1,53 @@
// 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 Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
didReceiveAttrs() {
let p = this.get('store').createRecord('page');
let m = this.get('store').createRecord('pageMeta');
p.set('id', this.get('block.id'));
p.set('orgId', this.get('block.orgId'));
p.set('documentId', this.get('document.id'));
p.set('contentType', this.get('block.contentType'));
p.set('pageType', this.get('block.pageType'));
p.set('title', this.get('block.title'));
p.set('body', this.get('block.body'));
p.set('rawBody', this.get('block.rawBody'));
p.set('excerpt', this.get('block.excerpt'));
m.set('pageId', this.get('block.id'));
m.set('orgId', this.get('block.orgId'));
m.set('documentId', this.get('document.id'));
m.set('rawBody', this.get('block.rawBody'));
m.set('config', this.get('block.config'));
m.set('externalSource', this.get('block.externalSource'));
this.set('page', p);
this.set('meta', m);
this.set('editorType', 'section/' + this.get('block.contentType') + '/type-editor');
},
actions: {
onCancel() {
this.attrs.onCancel();
},
onAction(page, meta) {
this.attrs.onAction(page, meta);
}
}
});

View file

@ -17,6 +17,13 @@ export default Ember.Component.extend({
actionLabel: "Save",
tip: "Short and concise title",
busy: false,
hasExcerpt: Ember.computed('page', function () {
return is.not.undefined(this.get('page.excerpt'));
}),
didReceiveAttrs() {
this._super(...arguments);
},
didRender() {
let self = this;
@ -30,10 +37,14 @@ export default Ember.Component.extend({
});
$("#page-title").removeClass("error");
$("#page-excerpt").removeClass("error");
$("#page-title").focus(function() {
$(this).select();
});
$("#page-excerpt").focus(function() {
$(this).select();
});
},
willDestroyElement() {
@ -80,6 +91,11 @@ export default Ember.Component.extend({
return;
}
if (this.get('hasExcerpt') && is.empty(this.get('page.excerpt'))) {
$("#page-excerpt").addClass("error").focus();
return;
}
this.attrs.onAction(this.get('page.title'));
},

View file

@ -0,0 +1,47 @@
// 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 Ember from 'ember';
export default Ember.Controller.extend({
sectionService: Ember.inject.service('section'),
actions: {
onCancel( /*page*/ ) {
this.transitionToRoute('document', {
queryParams: {
page: this.get('model.page.id')
}
});
},
onAction(page, meta) {
let self = this;
let block = this.get('model.block');
block.set('title', page.get('title'));
block.set('body', page.get('body'));
block.set('excerpt', page.get('excerpt'));
block.set('rawBody', meta.get('rawBody'));
block.set('config', meta.get('config'));
block.set('externalSource', meta.get('externalSource'));
this.get('sectionService').updateBlock(block).then(function () {
self.audit.record("edited-block");
self.transitionToRoute('document', {
queryParams: {
page: page.get('id')
}
});
});
}
}
});

View file

@ -0,0 +1,31 @@
// 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 Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
documentService: Ember.inject.service('document'),
folderService: Ember.inject.service('folder'),
sectionService: Ember.inject.service('section'),
model(params) {
let self = this;
this.audit.record("edited-block");
return Ember.RSVP.hash({
folder: self.modelFor('document').folder,
document: self.modelFor('document').document,
block: self.get('sectionService').getBlock(params.block_id),
});
}
});

View file

@ -0,0 +1 @@
{{document/block-editor document=model.document folder=model.folder block=model.block onCancel=(action 'onCancel') onAction=(action 'onAction')}}

View file

@ -50,6 +50,9 @@ export default Router.map(function () {
this.route('history', {
path: 'history'
});
this.route('block', {
path: 'block/:block_id'
});
});
this.route('customize', {

View file

@ -84,9 +84,19 @@ export default BaseService.extend({
});
},
// Returns reusable content block.
getBlock(blockId) {
return this.get('ajax').request(`sections/blocks/${blockId}`, {
method: 'GET'
}).then((response) => {
let data = this.get('store').normalize('block', response);
return this.get('store').push(data);
});
},
// Returns all available reusable content block for section.
getSpaceBlocks(folderId) {
return this.get('ajax').request(`sections/blocks/${folderId}`, {
return this.get('ajax').request(`sections/blocks/space/${folderId}`, {
method: 'GET'
}).then((response) => {
let data = [];
@ -98,5 +108,13 @@ export default BaseService.extend({
return data;
});
},
// Returns reusable content block.
updateBlock(block) {
return this.get('ajax').request(`sections/blocks/${block.id}`, {
method: 'PUT',
data: JSON.stringify(block)
});
}
});

View file

@ -29,6 +29,10 @@
&:hover {
@include ease-in();
> .actions {
display: inline-block;
}
> .details {
> .title {
color: $color-primary;
@ -72,6 +76,25 @@
margin-top: 5px;
}
}
> .actions {
display: none;
vertical-align: top;
text-align: center;
width: 20px;
margin-top: 5px;
opacity: 0.3;
> .material-icons, a {
color: $color-gray;
&:hover {
opacity: 1;
color: $color-primary;
}
}
}
}
}
}

View file

@ -0,0 +1 @@
{{component editorType document=document folder=folder page=page meta=meta onCancel=(action 'onCancel') onAction=(action 'onAction')}}

View file

@ -24,24 +24,31 @@
<div class="template-caption">Reusable Content</div>
<ul class="list">
{{#each blocks as |block|}}
<li class="item" {{action 'onInsertBlock' block}}>
<div class="icon">
<li class="item">
<div class="icon" {{action 'onInsertBlock' block}}>
<img class="img" src="/assets/img/section-saved.png" srcset="/assets/img/section-saved@2x.png" />
</div>
<div class="details">
<div class="details" {{action 'onInsertBlock' block}}>
<div class='title'>
{{block.title}}
</div>
<div class='desc'>{{block.excerpt}}</div>
<div class='desc'>By {{block.firstname}} {{block.lastname}}, {{time-ago block.created}} (used: {{ block.used }})</div>
</div>
<div class="actions">
{{#link-to 'document.block' folder.id folder.slug document.id document.slug block.id}}
<i class="material-icons">mode_edit</i>
{{/link-to}}
<div class="divider"></div>
<i class="material-icons">delete</i>
</div>
<div class="clearfix" />
</li>
{{/each}}
</ul>
{{else}}
<div class="divider"></div>
<div class="template-caption">No reusable content</div>
<div class="template-caption">Published, reusable sections appear below</div>
{{/if}}
</div>
</div>

View file

@ -6,6 +6,15 @@
<div class="tip">{{tip}}</div>
{{focus-input type='text' id="page-title" value=page.title class="mousetrap"}}
</div>
{{#if hasExcerpt}}
<div class="margin-top-30">
<div class="input-control">
<label>Excerpt</label>
<div class="tip">Short description</div>
{{textarea rows="3" id="page-excerpt" value=page.excerpt class="mousetrap"}}
</div>
</div>
{{/if}}
</div>
<div class="buttons pull-right">
{{#if busy}}

View file

@ -308,14 +308,6 @@ func DeleteDocumentPage(w http.ResponseWriter, r *http.Request) {
p.Context.Transaction = tx
_, err = p.DeletePage(documentID, pageID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
page, err := p.GetPage(pageID)
if err != nil {
log.IfErr(tx.Rollback())
@ -327,6 +319,14 @@ func DeleteDocumentPage(w http.ResponseWriter, r *http.Request) {
p.DecrementBlockUsage(page.BlockID)
}
_, err = p.DeletePage(documentID, pageID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
@ -376,13 +376,6 @@ func DeleteDocumentPages(w http.ResponseWriter, r *http.Request) {
p.Context.Transaction = tx
for _, page := range *model {
_, err = p.DeletePage(documentID, page.PageID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
pageData, err := p.GetPage(page.PageID)
if err != nil {
log.IfErr(tx.Rollback())
@ -393,6 +386,13 @@ func DeleteDocumentPages(w http.ResponseWriter, r *http.Request) {
if len(pageData.BlockID) > 0 {
p.DecrementBlockUsage(pageData.BlockID)
}
_, err = p.DeletePage(documentID, page.PageID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
}
log.IfErr(tx.Commit())

View file

@ -216,7 +216,9 @@ func init() {
log.IfErr(Add(RoutePrefixPrivate, "sections", []string{"GET", "OPTIONS"}, nil, GetSections))
log.IfErr(Add(RoutePrefixPrivate, "sections", []string{"POST", "OPTIONS"}, nil, RunSectionCommand))
log.IfErr(Add(RoutePrefixPrivate, "sections/refresh", []string{"GET", "OPTIONS"}, nil, RefreshSections))
log.IfErr(Add(RoutePrefixPrivate, "sections/blocks/{folderID}", []string{"GET", "OPTIONS"}, nil, GetBlocksForSpace))
log.IfErr(Add(RoutePrefixPrivate, "sections/blocks/space/{folderID}", []string{"GET", "OPTIONS"}, nil, GetBlocksForSpace))
log.IfErr(Add(RoutePrefixPrivate, "sections/blocks/{blockID}", []string{"GET", "OPTIONS"}, nil, GetBlock))
log.IfErr(Add(RoutePrefixPrivate, "sections/blocks/{blockID}", []string{"PUT", "OPTIONS"}, nil, UpdateBlock))
log.IfErr(Add(RoutePrefixPrivate, "sections/blocks", []string{"POST", "OPTIONS"}, nil, AddBlock))
// Links

View file

@ -229,6 +229,34 @@ func AddBlock(w http.ResponseWriter, r *http.Request) {
writeSuccessEmptyJSON(w)
}
// GetBlock returns requested reusable content block.
func GetBlock(w http.ResponseWriter, r *http.Request) {
method := "GetBlock"
p := request.GetPersister(r)
params := mux.Vars(r)
blockID := params["blockID"]
if len(blockID) == 0 {
writeMissingDataError(w, method, "blockID")
return
}
b, err := p.GetBlock(blockID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
json, err := json.Marshal(b)
if err != nil {
writeJSONMarshalError(w, method, "block", err)
return
}
writeSuccessBytes(w, json)
}
// GetBlocksForSpace returns available reusable content blocks for the space.
func GetBlocksForSpace(w http.ResponseWriter, r *http.Request) {
method := "GetBlocksForSpace"
@ -264,3 +292,48 @@ func GetBlocksForSpace(w http.ResponseWriter, r *http.Request) {
writeSuccessBytes(w, json)
}
// UpdateBlock inserts new reusable content block into database.
func UpdateBlock(w http.ResponseWriter, r *http.Request) {
method := "UpdateBlock"
p := request.GetPersister(r)
defer utility.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writeBadRequestError(w, method, "Bad payload")
return
}
b := entity.Block{}
err = json.Unmarshal(body, &b)
if err != nil {
writePayloadError(w, method, err)
return
}
if !p.CanUploadDocument(b.LabelID) {
writeForbiddenError(w)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.UpdateBlock(b)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
}

View file

@ -48,7 +48,7 @@ func (p *Persister) AddBlock(b entity.Block) (err error) {
// GetBlock returns requested reusable content block.
func (p *Persister) GetBlock(id string) (b entity.Block, err error) {
stmt, err := Db.Preparex("SELECT id, refid, orgid, labelid, userid, contenttype, pagetype, title, body, excerpt, rawbody, config, externalsource, used, created, revised FROM block WHERE orgid=? AND refid=?")
stmt, err := Db.Preparex("SELECT a.id, a.refid, a.orgid, a.labelid, a.userid, a.contenttype, a.pagetype, a.title, a.body, a.excerpt, a.rawbody, a.config, a.externalsource, a.used, a.created, a.revised, b.firstname, b.lastname FROM block a LEFT JOIN user b ON a.userid = b.refid WHERE a.orgid=? AND a.refid=?")
defer utility.Close(stmt)
if err != nil {
@ -67,7 +67,7 @@ func (p *Persister) GetBlock(id string) (b entity.Block, err error) {
// GetBlocksForSpace returns all reusable content scoped to given space.
func (p *Persister) GetBlocksForSpace(labelID string) (b []entity.Block, err error) {
err = Db.Select(&b, "SELECT a.id, a.refid, a.orgid, a.labelid, a.userid, a.contenttype, a.pagetype, a.title, a.body, a.excerpt, a.rawbody, a.config, a.externalsource, a.used, a.created, a.revised, b.firstname, b.lastname FROM block a LEFT JOIN user b ON a.userid = b.refid WHERE orgid=? AND labelid=? ORDER BY a.title", p.Context.OrgID, labelID)
err = Db.Select(&b, "SELECT a.id, a.refid, a.orgid, a.labelid, a.userid, a.contenttype, a.pagetype, a.title, a.body, a.excerpt, a.rawbody, a.config, a.externalsource, a.used, a.created, a.revised, b.firstname, b.lastname FROM block a LEFT JOIN user b ON a.userid = b.refid WHERE a.orgid=? AND a.labelid=? ORDER BY a.title", p.Context.OrgID, labelID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select GetBlocksForSpace org %s and label %s", p.Context.OrgID, labelID), err)