skillbox/client/src/components/ContentBlock.vue

331 lines
10 KiB
Vue

<template>
<div class="content-block__container hideable-element" :class="{'hideable-element--hidden': hidden}">
<div class="content-block" :class="specialClass">
<div class="block-actions" v-if="canEditContentBlock && editModule">
<user-widget v-bind="me" class="block-actions__user-widget content-block__user-widget"></user-widget>
<more-options-widget>
<li class="popover-links__link"><a @click="deleteContentBlock(contentBlock)">Löschen</a></li>
<li class="popover-links__link"><a @click="editContentBlock(contentBlock)">Bearbeiten</a></li>
</more-options-widget>
</div>
<div class="content-block__visibility">
<visibility-action
v-if="canEditModule"
:block="contentBlock"></visibility-action>
</div>
<h3 v-if="instrumentLabel !== ''" class="content-block__instrument-label">{{instrumentLabel}}</h3>
<h4 class="content-block__title" v-if="!contentBlock.indent">{{contentBlock.title}}</h4>
<component v-for="component in contentBlocksWithContentLists.contents"
:key="component.id"
:is="component.type"
v-bind="component">
</component>
</div>
<add-content-button :after="contentBlock" v-if="canEditModule"></add-content-button>
</div>
</template>
<script>
import TextBlock from '@/components/content-blocks/TextBlock';
import InstrumentWidget from '@/components/content-blocks/InstrumentWidget';
import Task from '@/components/content-blocks/Task';
import ImageBlock from '@/components/content-blocks/ImageBlock';
import ImageUrlBlock from '@/components/content-blocks/ImageUrlBlock';
import VideoBlock from '@/components/content-blocks/VideoBlock';
import LinkBlock from '@/components/content-blocks/LinkBlock';
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
import InfogramBlock from '@/components/content-blocks/InfogramBlock';
import GeniallyBlock from '@/components/content-blocks/GeniallyBlock';
import SubtitleBlock from '@/components/content-blocks/SubtitleBlock';
import ContentListBlock from '@/components/content-blocks/ContentListBlock';
import Assignment from '@/components/content-blocks/assignment/Assignment';
import Survey from '@/components/content-blocks/SurveyBlock';
import Solution from '@/components/content-blocks/Solution';
import AddContentButton from '@/components/AddContentButton';
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
import UserWidget from '@/components/UserWidget';
import VisibilityAction from '@/components/visibility/VisibilityAction';
import EyeIcon from '@/components/icons/EyeIcon';
import PenIcon from '@/components/icons/PenIcon';
import TrashIcon from '@/components/icons/TrashIcon';
import ModuleRoomSlug from '@/components/content-blocks/ModuleRoomSlug'
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';
import {meQuery} from '@/graphql/queries';
import {mapGetters} from 'vuex';
import {isHidden} from '@/helpers/content-block';
const instruments = {
base_communication: 'Sprache & Kommunikation',
base_society: 'Gesellschaft'
};
export default {
props: ['contentBlock', 'parent'],
name: 'content-block',
components: {
'text_block': TextBlock,
'basic_knowledge': InstrumentWidget, // for legacy
'instrument': InstrumentWidget,
'image_block': ImageBlock,
'image_url_block': ImageUrlBlock,
'video_block': VideoBlock,
'link_block': LinkBlock,
'document_block': DocumentBlock,
'infogram_block': InfogramBlock,
'genially_block': GeniallyBlock,
'subtitle': SubtitleBlock,
'content_list': ContentListBlock,
'module_room_slug': ModuleRoomSlug,
Survey,
Solution,
Assignment,
Task,
AddContentButton,
VisibilityAction,
EyeIcon,
PenIcon,
TrashIcon,
MoreOptionsWidget,
UserWidget
},
computed: {
...mapGetters(['editModule']),
canEditModule() {
return !this.contentBlock.indent && this.editModule;
},
specialClass() {
return `content-block--${this.contentBlock.type.toLowerCase()}`;
},
instrumentLabel() {
const contentType = this.contentBlock.type.toLowerCase();
if (!(contentType in instruments)) {
return '';
}
return `Instrument - ${instruments[contentType]}`;
},
canEditContentBlock() {
return this.contentBlock.mine && !this.contentBlock.indent;
},
contentBlocksWithContentLists() {
/*
collects all content_list_items in content_lists:
{
text_block,
content_list_item: [contents...],
content_list_item: [contents...],
text_block
} becomes
{
text_block,
content_list: [content_list_item: [contents...], content_list_item: [contents...]],
text_block
}
if there's only a single content_list_item it should not be displayed as list like so
{
text_block,
content_list_item: [text_block, image_block],
} becomes
{
text_block,
text_block,
image_block
}
*/
let contentList = [];
let startingIndex = 0;
let newContent = this.contentBlock.contents.reduce((newContents, content, index) => {
// collect content_list_items
if (content.type === 'content_list_item') {
contentList = [...contentList, content]
if (index === this.contentBlock.contents.length - 1) { // content is last element of contents array
startingIndex = this.updateStartingIndex(startingIndex, contentList);
return [...newContents, ...this.createContentListOrBlocks(contentList, startingIndex)];
}
return newContents;
} else {
// handle all other items and reset current content_list if necessary
if (contentList.length !== 0) {
newContents = [...newContents, ...this.createContentListOrBlocks(contentList, startingIndex), content];
startingIndex = this.updateStartingIndex(startingIndex, contentList);
contentList = [];
return newContents;
} else {
return [...newContents, content];
}
}
}, []);
return Object.assign({}, this.contentBlock, {
contents: this.removeSingleContentListItem(newContent, startingIndex)
});
},
schoolClass() {
return this.me.selectedClass;
},
hidden() {
return isHidden(this.contentBlock, this.schoolClass);
}
},
methods: {
editContentBlock(contentBlock) {
this.$store.dispatch('editContentBlock', contentBlock.id);
},
deleteContentBlock(contentBlock) {
const parent = this.parent;
const id = contentBlock.id;
this.$apollo.mutate({
mutation: DELETE_CONTENT_BLOCK_MUTATION,
variables: {
input: {
id
}
},
update(store, {data: {deleteContentBlock: {success}}}) {
try {
if (success) {
const query = CHAPTER_QUERY;
const variables = {
id: parent
};
const data = store.readQuery({query, variables});
data.chapter.contentBlocks.edges.splice(data.chapter.contentBlocks.edges.findIndex(edge => edge.node.id === id), 1);
store.writeQuery({query, variables, data});
}
} catch (e) {
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
}
}
});
},
createContentListOrBlocks(contentList, startingIndex) {
return [{
type: 'content_list',
contents: contentList,
id: contentList[0].id,
startingIndex
}];
},
updateStartingIndex(startingIndex, contentList) {
return contentList.length > 1 ? startingIndex + contentList.length : startingIndex;
},
removeSingleContentListItem(content, index) {
// just handle the case where we have one contentlistItem ( no index like "a)"" will be shown)
if (index > 0) {
return content;
};
let contentListIndex = content.findIndex(contentItem => contentItem.type === 'content_list');
if (contentListIndex < 0) {
return content;
}
return [...content.slice(0, contentListIndex), ...content[contentListIndex].contents[0].value, ...content.slice(contentListIndex + 1)];
}
},
data() {
return {
showVisibility: false,
me: {}
}
},
apollo: {
me: meQuery
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.content-block {
margin-bottom: 2.5em;
position: relative;
&__container {
position: relative;
}
&__title {
line-height: 1.5;
}
&__instrument-label {
margin-bottom: 0;
@include regular-text();
}
&__action-button {
cursor: pointer;
}
&__user-widget {
margin-right: 0;
}
&--base_communication {
@include content-box($color-accent-1-list);
.content-block__instrument-label {
color: $color-accent-1-dark;
}
}
&--task {
@include light-border(bottom);
.content-block__title {
color: $color-brand;
margin-top: $default-padding;
@include light-border(bottom);
@include desktop {
margin-top: 0;
}
}
}
&--base_society {
@include content-box($color-accent-2-list);
.content-block__instrument-label {
color: $color-accent-2-dark;
}
}
/deep/ p {
line-height: 1.5;
margin-bottom: 1em;
&:last-child {
margin-bottom: 0;
}
}
/deep/ .text-block {
ul {
padding-left: 25px;
}
li {
list-style: disc;
line-height: 1.5;
}
}
}
</style>