Add content block edit modal
Also clean up the state store. Also change the properties of the content block contents on the server
This commit is contained in:
parent
8067ca439d
commit
ef48f5afb6
|
|
@ -10,6 +10,7 @@
|
|||
import SimpleLayout from '@/layouts/SimpleLayout';
|
||||
import Modal from '@/components/Modal';
|
||||
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
|
||||
import EditContentBlockWizard from '@/components/content-block-form/EditContentBlockWizard';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
|
@ -18,7 +19,8 @@
|
|||
DefaultLayout,
|
||||
SimpleLayout,
|
||||
Modal,
|
||||
NewContentBlockWizard
|
||||
NewContentBlockWizard,
|
||||
EditContentBlockWizard
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
:content-block="contentBlock"
|
||||
class="content-block__visibility-menu"
|
||||
></visibility-popover>
|
||||
<a><pen-icon class="content-block__action-icon"></pen-icon></a>
|
||||
<a @click="editContentBlock()">
|
||||
<pen-icon class="content-block__action-icon"></pen-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 class="content-block__title">{{contentBlock.title}}</h4>
|
||||
|
|
@ -70,6 +72,9 @@
|
|||
methods: {
|
||||
toggleVisibility() {
|
||||
this.showVisibility = !this.showVisibility;
|
||||
},
|
||||
editContentBlock() {
|
||||
this.$store.dispatch('editContentBlock', this.contentBlock.id);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
v-on:add-element="addElement"
|
||||
:index="-1"
|
||||
></add-content-element>
|
||||
<div v-for="(element, index) in contentBlock.elements" :key="index" class="content-block-form__element">
|
||||
<div v-for="(element, index) in contentBlock.contents" :key="index" class="content-block-form__element">
|
||||
<component
|
||||
class="content-block-form__element-component"
|
||||
:is="type(element)"
|
||||
|
|
@ -93,9 +93,13 @@
|
|||
return 'content-block-element-chooser-widget'
|
||||
},
|
||||
_updateProperty(value, index, key) {
|
||||
this.localContentBlock.elements.splice(index, 1, {
|
||||
...this.localContentBlock.elements[index],
|
||||
const content = this.localContentBlock.contents[index];
|
||||
this.localContentBlock.contents.splice(index, 1, {
|
||||
...content,
|
||||
value: {
|
||||
...content.value,
|
||||
[key]: value
|
||||
}
|
||||
});
|
||||
},
|
||||
changeLinkUrl(value, index) {
|
||||
|
|
@ -115,10 +119,10 @@
|
|||
this._updateProperty(value, index, 'text')
|
||||
},
|
||||
removeElement(index) {
|
||||
this.localContentBlock.elements.splice(index, 1);
|
||||
this.localContentBlock.contents.splice(index, 1);
|
||||
},
|
||||
addElement(index) {
|
||||
this.localContentBlock.elements.splice(index + 1, 0, {})
|
||||
this.localContentBlock.contents.splice(index + 1, 0, {})
|
||||
},
|
||||
updateTitle(title) {
|
||||
this.localContentBlock.title = title;
|
||||
|
|
@ -126,37 +130,46 @@
|
|||
},
|
||||
changeType(index, type) {
|
||||
let el = {
|
||||
type: type
|
||||
type: type,
|
||||
value: {}
|
||||
};
|
||||
switch (type) {
|
||||
case 'text_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
text: ''
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'link_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
text: '',
|
||||
url: ''
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'video_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
url: ''
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'document_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
url: ''
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
this.localContentBlock.elements.splice(index, 1, el);
|
||||
this.localContentBlock.contents.splice(index, 1, el);
|
||||
},
|
||||
save() {
|
||||
if (!this.localContentBlock.title) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<content-block-form
|
||||
:content-block="contentBlock"
|
||||
@save="saveContentBlock"
|
||||
@hide="hideModal"
|
||||
></content-block-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
|
||||
|
||||
import store from '@/store/index';
|
||||
|
||||
import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql';
|
||||
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
|
||||
import CONTENT_BLOCK_QUERY from '@/graphql/gql/contentBlockQuery.gql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContentBlockForm
|
||||
},
|
||||
|
||||
methods: {
|
||||
hideModal() {
|
||||
this.$store.dispatch('resetCurrentContentBlock');
|
||||
this.$store.dispatch('hideModal');
|
||||
},
|
||||
saveContentBlock(contentBlock) {
|
||||
this.$apollo.mutate({
|
||||
mutation: NEW_CONTENT_BLOCK_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
contentBlock: {
|
||||
title: contentBlock.title,
|
||||
contents: contentBlock.contents.filter(value => Object.keys(value).length > 0)
|
||||
},
|
||||
after: this.$store.state.contentBlockPosition.after,
|
||||
parent: this.$store.state.contentBlockPosition.parent
|
||||
}
|
||||
},
|
||||
refetchQueries: [{
|
||||
query: MODULE_DETAILS_QUERY,
|
||||
variables: {
|
||||
slug: store.state.moduleSlug
|
||||
}
|
||||
}]
|
||||
}).then(() => {
|
||||
this.hideModal();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
// debugger;
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
contentBlock: {}
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
contentBlock() {
|
||||
return {
|
||||
query: CONTENT_BLOCK_QUERY,
|
||||
variables: {
|
||||
id: store.state.currentContentBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
methods: {
|
||||
hideModal() {
|
||||
this.$store.dispatch('resetContentBlock');
|
||||
this.$store.dispatch('resetContentBlockPosition');
|
||||
this.$store.dispatch('hideModal');
|
||||
},
|
||||
saveContentBlock(contentBlock) {
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
input: {
|
||||
contentBlock: {
|
||||
title: contentBlock.title,
|
||||
contents: contentBlock.elements.filter(value => Object.keys(value).length > 0)
|
||||
contents: contentBlock.contents.filter(value => Object.keys(value).length > 0)
|
||||
},
|
||||
after: this.$store.state.contentBlockPosition.after,
|
||||
parent: this.$store.state.contentBlockPosition.parent
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
return {
|
||||
contentBlock: {
|
||||
title: '',
|
||||
elements: [
|
||||
contents: [
|
||||
{}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</p>
|
||||
|
||||
<input class="document-form__document-link skillbox-input"
|
||||
:value="url" v-on:input="$emit('document-change-url', $event.target.value, index)"
|
||||
:value="value.url" v-on:input="$emit('document-change-url', $event.target.value, index)"
|
||||
placeholder="URL einfügen...">
|
||||
|
||||
</div>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
import InfoIcon from '@/components/icons/InfoIcon';
|
||||
|
||||
export default {
|
||||
props: ['url', 'index'],
|
||||
props: ['value', 'index'],
|
||||
|
||||
components: {
|
||||
InfoIcon
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<div class="link-form">
|
||||
<input placeholder="Name erfassen..." class="link-form__text skillbox-input" :value="text" v-on:input="$emit('link-change-text', $event.target.value, index)">
|
||||
<input placeholder="Name erfassen..." class="link-form__text skillbox-input" :value="value.text" v-on:input="$emit('link-change-text', $event.target.value, index)">
|
||||
|
||||
<input placeholder="URL einfügen..." class="link-form__url skillbox-input" :value="url" v-on:input="$emit('link-change-url', $event.target.value, index)">
|
||||
<input placeholder="URL einfügen..." class="link-form__url skillbox-input" :value="value.url" v-on:input="$emit('link-change-url', $event.target.value, index)">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['text', 'url', 'index']
|
||||
props: ['value', 'index']
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['text', 'index']
|
||||
props: ['value', 'index'],
|
||||
|
||||
computed: {
|
||||
text() {
|
||||
return this.value.text.replace(/(<([^>]+)>)/ig, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@
|
|||
|
||||
<input class="video-form__video-link skillbox-input"
|
||||
placeholder="Bsp: https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
:value="url" v-on:input="$emit('video-change-url', $event.target.value, index)">
|
||||
:value="value.url" v-on:input="$emit('video-change-url', $event.target.value, index)">
|
||||
</div>
|
||||
|
||||
<div v-if="isYoutube">
|
||||
<youtube-embed :url="url"></youtube-embed>
|
||||
<youtube-embed :url="value.url"></youtube-embed>
|
||||
</div>
|
||||
<div v-if="isVimeo">
|
||||
<vimeo-embed :url="url"></vimeo-embed>
|
||||
<vimeo-embed :url="value.url"></vimeo-embed>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
import {isVimeoUrl, isYoutubeUrl} from '@/helpers/video';
|
||||
|
||||
export default {
|
||||
props: ['url', 'index'],
|
||||
props: ['value', 'index'],
|
||||
|
||||
components: {
|
||||
InfoIcon,
|
||||
|
|
@ -41,10 +41,10 @@
|
|||
|
||||
computed: {
|
||||
isYoutube() {
|
||||
return isYoutubeUrl(this.url);
|
||||
return isYoutubeUrl(this.value.url);
|
||||
},
|
||||
isVimeo() {
|
||||
return isVimeoUrl(this.url);
|
||||
return isVimeoUrl(this.value.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,18 @@ const consoleLink = new ApolloLink((operation, forward) => {
|
|||
|
||||
const composedLink = ApolloLink.from([consoleLink, httpLink]);
|
||||
|
||||
const cache = new InMemoryCache({
|
||||
cacheRedirects: {
|
||||
Query: {
|
||||
contentBlock: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ContentBlockNode', id: args.id})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create the apollo client
|
||||
export default new ApolloClient({
|
||||
link: composedLink,
|
||||
// link: httpLink,
|
||||
cache: new InMemoryCache(),
|
||||
cache: cache,
|
||||
connectToDevTools: true
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
#import "./fragments/contentBlockParts.gql"
|
||||
query ContentBlockQuery($id: ID!) {
|
||||
contentBlock(id: $id) {
|
||||
...ContentBlockParts
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
manual: true,
|
||||
result({data, loading, networkStatus}) {
|
||||
if (!loading) {
|
||||
const cleanedData = this.$getRidOfEdges(data)
|
||||
const cleanedData = this.$getRidOfEdges(data);
|
||||
this.module = cleanedData.modules[0] || {};
|
||||
}
|
||||
}
|
||||
|
|
@ -37,21 +37,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$store.subscribe((mutation, state) => {
|
||||
if (mutation.type === 'updateContentBlocks' && state.updateContentBlocks) {
|
||||
this.updateQuery();
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateQuery() {
|
||||
this.$apollo.queries.moduleQuery.refetch();
|
||||
this.$store.dispatch('resetUpdateContentBlocksFlag');
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
module: {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export default new Vuex.Store({
|
|||
contentBlockPosition: {},
|
||||
scrollPosition: 0,
|
||||
moduleSlug: 'mein-neues-umfeld',
|
||||
updateContentBlocks: false,
|
||||
filterForGroup: false
|
||||
filterForGroup: false,
|
||||
currentContentBlock: ''
|
||||
},
|
||||
|
||||
getters: {},
|
||||
|
|
@ -26,9 +26,16 @@ export default new Vuex.Store({
|
|||
document.body.classList.remove('no-scroll'); // won't get at the body any other way
|
||||
commit('setModal', false);
|
||||
},
|
||||
resetContentBlock({commit}) {
|
||||
resetContentBlockPosition({commit}) {
|
||||
commit('setContentBlockPosition', {});
|
||||
},
|
||||
resetCurrentContentBlock({commit}) {
|
||||
commit('setCurrentContentBlock', '');
|
||||
},
|
||||
editContentBlock({commit, dispatch}, payload) {
|
||||
commit('setCurrentContentBlock', payload);
|
||||
dispatch('showModal', 'edit-content-block-wizard');
|
||||
},
|
||||
addContentBlock({commit, dispatch}, payload) {
|
||||
commit('setContentBlockPosition', payload);
|
||||
dispatch('showModal', 'new-content-block-wizard');
|
||||
|
|
@ -37,12 +44,6 @@ export default new Vuex.Store({
|
|||
document.body.classList.add('no-scroll'); // won't get at the body any other way
|
||||
commit('setModal', payload);
|
||||
},
|
||||
updateContentBlocks({commit}) {
|
||||
commit('updateContentBlocks', true);
|
||||
},
|
||||
resetUpdateContentBlocksFlag({commit}) {
|
||||
commit('updateContentBlocks', false);
|
||||
},
|
||||
setFilterForGroup({commit}, payload) {
|
||||
commit('setFilterForGroup', payload);
|
||||
}
|
||||
|
|
@ -61,12 +62,12 @@ export default new Vuex.Store({
|
|||
setNewContentBlock(state, payload) {
|
||||
state.newContentBlock = payload;
|
||||
},
|
||||
updateContentBlocks(state, payload) {
|
||||
state.updateContentBlocks = payload;
|
||||
},
|
||||
setContentBlockPosition(state, payload) {
|
||||
state.contentBlockPosition = payload;
|
||||
},
|
||||
setCurrentContentBlock(state, payload) {
|
||||
state.currentContentBlock = payload;
|
||||
},
|
||||
setFilterForGroup(state, payload) {
|
||||
state.filterForGroup = payload;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,9 @@ class InputTypes(graphene.Enum):
|
|||
document_block = 'document_block'
|
||||
|
||||
|
||||
class ContentElementInput(InputObjectType):
|
||||
class ContentElementValueInput(InputObjectType):
|
||||
# we'll handle this with a single input, even tho it would be nice to have a type for every different possibility
|
||||
# see discussion at https://github.com/graphql/graphql-js/issues/207
|
||||
type = InputTypes(required=True)
|
||||
text = graphene.String(description='To be used for link_block, text_block types')
|
||||
url = graphene.String(description='To be used for link, basic_knowledge, image_block types')
|
||||
description = graphene.String(description='To be used for basic_knowledge type')
|
||||
|
|
@ -24,6 +23,11 @@ class ContentElementInput(InputObjectType):
|
|||
task_text = graphene.String(description='To be used for task type')
|
||||
|
||||
|
||||
class ContentElementInput(InputObjectType):
|
||||
type = InputTypes(required=True)
|
||||
value = ContentElementValueInput()
|
||||
|
||||
|
||||
class ContentBlockInput(InputObjectType):
|
||||
title = graphene.String(required=True)
|
||||
type = graphene.String()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def handle_content_blocks(content_data):
|
|||
new_contents.append({
|
||||
'type': 'text_block',
|
||||
'value': {
|
||||
'text': '<p>{}</p>'.format(bleach.clean(content['text']))
|
||||
'text': '<p>{}</p>'.format(bleach.clean(content['value']['text']))
|
||||
}})
|
||||
elif content['type'] == 'student_entry':
|
||||
pass
|
||||
|
|
@ -31,8 +31,8 @@ def handle_content_blocks(content_data):
|
|||
new_contents.append({
|
||||
'type': 'link_block',
|
||||
'value': {
|
||||
'text': bleach.clean(content['text']),
|
||||
'url': bleach.clean(content['url'])
|
||||
'text': bleach.clean(content['value']['text']),
|
||||
'url': bleach.clean(content['value']['url'])
|
||||
}
|
||||
})
|
||||
elif content['type'] == 'task':
|
||||
|
|
@ -41,13 +41,13 @@ def handle_content_blocks(content_data):
|
|||
new_contents.append({
|
||||
'type': 'video_block',
|
||||
'value': {
|
||||
'url': bleach.clean(content['url'])
|
||||
'url': bleach.clean(content['value']['url'])
|
||||
}})
|
||||
elif content['type'] == 'document_block':
|
||||
new_contents.append({
|
||||
'type': 'document_block',
|
||||
'value': {
|
||||
'url': bleach.clean(content['url'])
|
||||
'url': bleach.clean(content['value']['url'])
|
||||
}})
|
||||
|
||||
return new_contents
|
||||
|
|
|
|||
Loading…
Reference in New Issue