Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Posts workflow rework #1857

Merged
merged 12 commits into from Aug 28, 2018
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@

- Harvest sources are now filterable through the harvest source create/edit admin form [#1812](https://github.com/opendatateam/udata/pull/1812)
- Static assets are now compatible with long-term caching (ie. their hash is present in the filename) [#1826](https://github.com/opendatateam/udata/pull/1826)
- Post UIs have been reworked: publication date, publish/unpublish action, save and continue editing, dynamic sidebar, alignments fixes... [#1857](https://github.com/opendatateam/udata/pull/1857)

### Minor changes

Expand Down
1 change: 1 addition & 0 deletions js/admin.routes.js
Expand Up @@ -198,6 +198,7 @@ router.map({
}
},
'/post/new/': {
name: 'post-new',
component(resolve) {
require(['./views/post-wizard.vue'], resolve);
}
Expand Down
3 changes: 2 additions & 1 deletion js/components/dataset/card.vue
Expand Up @@ -22,7 +22,8 @@
<span class="fa fa-files-o fa-fw"></span>
{{ dataset.resources.length }}
</li>
<li v-if="dataset.spatial && dataset.spatial.zones" v-tooltip :title="_('Territorial coverage')">
<li v-if="dataset.spatial && dataset.spatial.zones && dataset.spatial.zones.length > 0" v-tooltip
:title="_('Territorial coverage')">
<span class="fa fa-map-marker fa-fw"></span>
{{ dataset.spatial.zones[0].name }}
</li>
Expand Down
45 changes: 38 additions & 7 deletions js/components/form-layout.vue
@@ -1,16 +1,22 @@
<template>
<div class="content-wrapper">
<div class="content-wrapper form-layout">
<notification-zone></notification-zone>
<section class="content">
<box :title="title" :icon="icon" boxclass="box-solid" :footer="true"
:loading="model ? model.loading : true">
<slot></slot>
<footer slot="footer">
<button class="btn btn-primary" v-if="save" @click.prevent="save">
{{ _('Save') }}
</button>
<button class="btn btn-warning pull-right" v-if="cancel"
@click.prevent="cancel">{{ _('Cancel') }}</button>
<footer class="form-actions" slot="footer">
<div class="btn-toolbar left-actions">
<button class="btn btn-warning" v-if="cancel"
@click.prevent="cancel">{{ _('Cancel') }}</button>
<slot name="left-actions"></slot>
</div>
<div class="btn-toolbar right-actions">
<button class="btn btn-primary" v-if="save" @click.prevent="save">
{{ _('Save') }}
</button>
<slot name="right-actions"></slot>
</div>
</footer>
</box>
<slot name="extras"></slot>
Expand All @@ -35,3 +41,28 @@ export default {
components: {Box, NotificationZone}
};
</script>

<style lang="less">
.form-layout {
.form-actions {
display: flex;

.btn-toolbar {
flex: 1 0 50%;
display: flex;
> .btn {
float: none;
}
}

.left-actions {
justify-content: flex-start;
}

.right-actions {
justify-content: flex-end;
}
}
}
</style>

9 changes: 9 additions & 0 deletions js/components/post/content.vue
Expand Up @@ -20,6 +20,9 @@
<image-button :src="post.image" :size="150"
:endpoint="endpoint">
</image-button>
<p v-if="published"><strong>
{{ _('Published on {date}', {date: published}) }}
</strong></p>
<p v-if="post.headline" class="lead">{{post.headline}}</p>
<div v-markdown="post.content"></div>
</box>
Expand All @@ -30,6 +33,8 @@
import API from 'api';
import Box from 'components/containers/box.vue';
import ImageButton from 'components/widgets/image-button.vue';
import moment from 'moment';


export default {
name: 'post-content',
Expand All @@ -49,6 +54,10 @@ export default {
var operation = API.posts.operations.post_image;
return operation.urlify({post: this.post.id});
}
},
published() {
if (!this.post.published) return;
return moment(this.post.published).format('LLL');
}
},
events: {
Expand Down
8 changes: 3 additions & 5 deletions js/components/post/form.vue
@@ -1,6 +1,6 @@
<template>
<div>
<vertical-form v-ref:form :fields="fields" :model="post"></vertical-form>
<vertical-form v-ref:form :fields="fields" :model="post"></vertical-form>
</div>
</template>

Expand All @@ -25,14 +25,12 @@ export default {
label: this._('Headline')
}, {
id: 'content',
label: this._('Content')
label: this._('Content'),
rows: 12
}, {
id: 'tags',
label: this._('Tags'),
widget: 'tag-completer'
}, {
id: 'private',
label: this._('Draft')
}]
};
},
Expand Down
14 changes: 7 additions & 7 deletions js/components/post/list.vue
@@ -1,13 +1,13 @@
<template>
<div>
<datatable :title="_('Posts') " icon="newspaper-o"
<datatable :title="_('Posts')" icon="newspaper-o"
boxclass="posts-widget"
:fields="fields"
:p="posts"
:empty="_('No post')">
<footer slot="footer">
<button type="button" class="btn btn-primary btn-flat btn-sm"
v-link.literal="/post/new/">
v-link="{name: 'post-new'}">
<span class="fa fa-fw fa-plus"></span>
<span v-i18n="New"></span>
</button>
Expand All @@ -21,7 +21,7 @@ import Datatable from 'components/datatable/widget.vue';

export default {
name: 'posts-list',
MASK: ['id', 'name', 'created_at', 'last_modified', 'private'],
MASK: ['id', 'name', 'created_at', 'last_modified', 'published'],
props: ['posts'],
components: {Datatable},
data() {
Expand All @@ -46,11 +46,11 @@ export default {
type: 'timeago',
width: 120
}, {
label: this._('Access'),
key: 'private',
sort: 'private',
label: this._('Publication'),
key: 'published',
sort: 'published',
align: 'left',
type: 'visibility',
type: 'timeago',
width: 120
}]
};
Expand Down
53 changes: 53 additions & 0 deletions js/components/post/publish-modal.vue
@@ -0,0 +1,53 @@
<template>
<div>
<modal :title="_('Publish')"
class="modal-warning post-publish-modal"
v-ref:modal>

<div class="modal-body">
<p class="lead text-center">
{{ _('You are about to publish this post') }}
</p>
<p class="lead text-center">
{{ _('Are you sure?') }}
</p>
</div>

<footer class="modal-footer text-center">
<button type="button" class="btn btn-outline pull-left"
@click="$refs.modal.close">
{{ _('Cancel') }}
</button>
<button type="button" class="btn btn-outline"
@click="confirm">
{{ _('Confirm') }}
</button>
</footer>
</modal>
</div>
</template>

<script>
import API from 'api';
import Modal from 'components/modal.vue';

export default {
components: {Modal},
props: {
post: Object,
},
methods: {
confirm() {
API.posts.publish_post({post: this.post.id}, (response) => {
this.post.on_fetched(response);
this.$dispatch('notify', {
autoclose: true,
title: this._('Post published'),
});
this.$refs.modal.close();
this.$go({name: 'post', params: {oid: this.post.id}});
});
}
}
};
</script>
53 changes: 53 additions & 0 deletions js/components/post/unpublish-modal.vue
@@ -0,0 +1,53 @@
<template>
<div>
<modal :title="_('Unpublish')"
class="modal-danger post-unpublish-modal"
v-ref:modal>

<div class="modal-body">
<p class="lead text-center">
{{ _('You are about to unpublish this post') }}
</p>
<p class="lead text-center">
{{ _('Are you sure?') }}
</p>
</div>

<footer class="modal-footer text-center">
<button type="button" class="btn btn-warning pull-left"
@click="$refs.modal.close">
{{ _('Cancel') }}
</button>
<button type="button" class="btn btn-outline"
@click="confirm">
{{ _('Confirm') }}
</button>
</footer>
</modal>
</div>
</template>

<script>
import API from 'api';
import Modal from 'components/modal.vue';

export default {
components: {Modal},
props: {
post: Object,
},
methods: {
confirm() {
API.posts.unpublish_post({post: this.post.id}, (response) => {
this.post.on_fetched(response);
this.$dispatch('notify', {
autoclose: true,
title: this._('Post unpublished'),
});
this.$refs.modal.close();
this.$go({name: 'post', params: {oid: this.post.id}});
});
}
}
};
</script>
10 changes: 8 additions & 2 deletions js/locales/udata.en.json
Expand Up @@ -11,7 +11,6 @@
"About": "About",
"Accept": "Accept",
"Accepted": "Accepted",
"Access": "Access",
"Acronym": "Acronym",
"Active": "Active",
"Add": "Add",
Expand Down Expand Up @@ -105,7 +104,6 @@
"Done with errors": "Done with errors",
"Download": "Download",
"Downloads": "Downloads",
"Draft": "Draft",
"Drag a file here": "Drag a file here",
"Drag a picture here": "Drag a picture here",
"Drop resource": "Drop resource",
Expand Down Expand Up @@ -268,6 +266,8 @@
"Pick the active badges": "Pick the active badges",
"Post": "Post",
"Post deleted": "Post deleted",
"Post published": "Post published",
"Post unpublished": "Post unpublished",
"Posts": "Posts",
"Preview": "Preview",
"Previous": "Previous",
Expand All @@ -277,6 +277,7 @@
"Profile": "Profile",
"Proposing up-to-date and incremental data makes it possible for reusers to establish datavisualisations on the long term.": "Proposing up-to-date and incremental data makes it possible for reusers to establish datavisualisations on the long term.",
"Public": "Public",
"Publication": "Publication",
"Publication date": "Publication date",
"Publish": "Publish",
"Publish a new dataset": "Publish a new dataset",
Expand All @@ -287,6 +288,7 @@
"Publish in your own name": "Publish in your own name",
"Publish some content": "Publish some content",
"Published on": "Published on",
"Published on {date}": "Published on {date}",
"Quality": "Quality",
"Read the documentation to insert more than one dataset": "Read the documentation to insert more than one dataset",
"Reason": "Reason",
Expand All @@ -310,6 +312,7 @@
"Role \"{role}\" is required": "Role \"{role}\" is required",
"Roles": "Roles",
"Save": "Save",
"Save and continue": "Save and continue",
"Schedule": "Schedule",
"Scheduling": "Scheduling",
"Score:": "Score:",
Expand Down Expand Up @@ -388,6 +391,7 @@
"Unfollow": "Unfollow",
"Unique visitors": "Unique visitors",
"Unknown error while communicating with the server": "Unknown error while communicating with the server",
"Unpublish": "Unpublish",
"Unrecoverable error - this browser does not permit file uploading of any kind due to serious bugs in iOS8 Safari. Please use iOS8 Chrome until Apple fixes these issues.": "Unrecoverable error - this browser does not permit file uploading of any kind due to serious bugs in iOS8 Safari. Please use iOS8 Chrome until Apple fixes these issues.",
"Unschedule": "Unschedule",
"Unsupported ISO-8601 date format": "Unsupported ISO-8601 date format",
Expand Down Expand Up @@ -423,6 +427,8 @@
"You are about to delete this reuse": "You are about to delete this reuse",
"You are about to delete this topic": "You are about to delete this topic",
"You are about to delete your profile.": "You are about to delete your profile.",
"You are about to publish this post": "You are about to publish this post",
"You are about to unpublish this post": "You are about to unpublish this post",
"You are about to unschedule this harvest source": "You are about to unschedule this harvest source",
"You are about to validate (or not) this harvest source.": "You are about to validate (or not) this harvest source.",
"You are currently involved in all discussions!": "You are currently involved in all discussions!",
Expand Down
17 changes: 15 additions & 2 deletions js/models/post.js
@@ -1,18 +1,31 @@
import {Model} from 'models/base';
import Dataset from 'models/dataset';
import Reuse from 'models/reuse';
import log from 'logger';


export default class Post extends Model {
fetch(ident) {
fetch(ident, mask) {
ident = ident || this.id || this.slug;
const options = {post: ident};
if (mask) {
options['X-Fields'] = mask;
}
if (ident) {
this.$api('posts.get_post', {post: ident}, this.on_fetched);
this.$api('posts.get_post', options, this.on_fetched);
} else {
log.error('Unable to fetch Post: no identifier specified');
}
return this;
}

on_fetched(data) {
super.on_fetched(data);
// Cast lists to benefit from helpers
this.datasets = this.datasets.map(d => new Dataset({data: d}));
this.reuses = this.reuses.map(r => new Reuse({data: r}));
}

update(data, on_success, on_error) {
this.$api('posts.update_post', {
post: this.id,
Expand Down