skillbox/client/src/components/ContentBlock.vue

369 lines
9.7 KiB
Vue

<template>
<div
:class="{'hideable-element--greyed-out': hidden}"
class="content-block__container hideable-element content-list__parent"
>
<div
:class="specialClass"
:style="instrumentStyle"
class="content-block"
data-cy="content-block"
>
<div
class="block-actions"
v-if="canEditContentBlock && editMode"
>
<user-widget
v-bind="me"
class="block-actions__user-widget content-block__user-widget"
/>
<more-options-widget>
<li class="popover-links__link">
<popover-link
data-cy="delete-content-block-link"
text="Löschen"
@link-action="deleteContentBlock(contentBlock)"
/>
</li>
<li class="popover-links__link">
<popover-link
text="Bearbeiten"
@link-action="editContentBlock(contentBlock)"
/>
</li>
</more-options-widget>
</div>
<div class="content-block__visibility">
<visibility-action
:block="contentBlock"
v-if="canEditModule"
/>
</div>
<h3
class="content-block__instrument-label"
data-cy="instrument-label"
:style="instrumentLabelStyle"
v-if="instrumentLabel !== ''"
>
{{ instrumentLabel }}
</h3>
<h4
class="content-block__title"
v-if="!contentBlock.indent"
>
{{ contentBlock.title }}
</h4>
<content-component
:component="component"
:root="root"
:parent="contentBlock"
:bookmarks="contentBlock.bookmarks"
:notes="contentBlock.notes"
:edit-mode="editMode"
v-for="component in contentBlocksWithContentLists.contents"
:key="component.id"
/>
</div>
<add-content-button
:where="{after: contentBlock}"
v-if="canEditModule"
/>
</div>
</template>
<script>
import AddContentButton from '@/components/AddContentButton';
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
import UserWidget from '@/components/UserWidget';
import VisibilityAction from '@/components/visibility/VisibilityAction';
import CHAPTER_QUERY from '@/graphql/gql/queries/chapterQuery.gql';
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';
import me from '@/mixins/me';
import {hidden} from '@/helpers/visibility';
import {CONTENT_TYPE} from '@/consts/types';
import PopoverLink from '@/components/ui/PopoverLink';
import {removeAtIndex} from '@/graphql/immutable-operations';
import {EDIT_CONTENT_BLOCK_PAGE} from '@/router/module.names';
import {instrumentCategory} from '@/helpers/instrumentType';
const ContentComponent = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ContentComponent');
export default {
name: 'ContentBlock',
props: {
contentBlock: {
type: Object,
default: () => ({}),
},
parent: {
type: Object,
default: () => ({}),
},
editMode: {
type: Boolean,
default: true,
},
},
mixins: [me],
components: {
PopoverLink,
ContentComponent,
AddContentButton,
VisibilityAction,
MoreOptionsWidget,
UserWidget,
},
computed: {
canEditModule() {
return !this.contentBlock.indent && this.editMode;
},
specialClass() {
return `content-block--${this.contentBlock.type.toLowerCase()}`;
},
isInstrumentBlock() {
return !!this.contentBlock.instrumentCategory;
},
instrumentStyle() {
if (this.isInstrumentBlock) {
return {
backgroundColor: this.contentBlock.instrumentCategory.background
};
}
return {};
},
instrumentLabel() {
const contentType = this.contentBlock.type.toLowerCase();
if (contentType.startsWith('base')) { // all legacy instruments start with `base`
return instrumentCategory(contentType);
}
if (this.isInstrumentBlock) {
return instrumentCategory(this.contentBlock.instrumentCategory.name);
}
return '';
},
instrumentLabelStyle() {
if (this.isInstrumentBlock) {
return {
color: this.contentBlock.instrumentCategory.foreground
};
}
return {};
},
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
}
*/
let contentList = [];
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
let updatedContent = [...newContents, ...this.createContentListOrBlocks(contentList)];
return updatedContent;
}
return newContents;
} else {
// handle all other items and reset current content_list if necessary
if (contentList.length !== 0) {
newContents = [...newContents, ...this.createContentListOrBlocks(contentList), content];
contentList = [];
return newContents;
} else {
return [...newContents, content];
}
}
}, []);
return Object.assign({}, this.contentBlock, {
contents: newContent,
});
},
hidden() {
return hidden({
block: this.contentBlock,
schoolClass: this.schoolClass,
type: CONTENT_TYPE,
});
},
root() {
// we need the root content block id, not the generated content block if inside a content list block
return this.contentBlock.root ? this.contentBlock.root : this.contentBlock.id;
},
},
methods: {
editContentBlock(contentBlock) {
const route = {
name: EDIT_CONTENT_BLOCK_PAGE,
params: {
id: contentBlock.id,
},
};
this.$router.push(route);
},
deleteContentBlock(contentBlock) {
this.$modal.open('confirm').then(() => {
this.doDeleteContentBlock(contentBlock);
})
.catch();
},
doDeleteContentBlock(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}}}) {
if (success) {
const query = CHAPTER_QUERY;
const variables = {
id: parent.id,
};
const {chapter} = store.readQuery({query, variables});
const index = chapter.contentBlocks.findIndex(contentBlock => contentBlock.id === id);
const contentBlocks = removeAtIndex(chapter.contentBlocks, index);
const data = {
chapter: {
...chapter,
contentBlocks,
},
};
store.writeQuery({query, variables, data});
}
},
});
},
createContentListOrBlocks(contentList) {
return [{
type: 'content_list',
contents: contentList,
id: contentList[0].id,
}];
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
.content-block {
margin-bottom: $section-spacing;
position: relative;
&__container {
position: relative;
}
&__title {
line-height: 1.5;
margin-top: -0.5rem; // to offset the 1.5 line height, it leaves a padding on top
}
&__instrument-label {
margin-bottom: $medium-spacing;
@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;
margin-bottom: $large-spacing;
@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;
}
}
&--base_interdisciplinary {
@include content-box($color-accent-4-list);
.content-block__instrument-label {
color: $color-accent-4-dark;
}
}
&--instrument {
@include content-box-base;
}
/deep/ p {
line-height: 1.5;
margin-bottom: 1em;
&:last-child {
margin-bottom: 0;
}
}
/deep/ .text-block {
ul {
@include list-parent;
}
li {
@include list-child;
line-height: 1.5;
}
}
}
</style>