Merged in feature/nested-block (pull request #26)
Feature/nested block Approved-by: Ramon Wenger <ramon.wenger@iterativ.ch>
This commit is contained in:
commit
a1234f6688
|
|
@ -3,19 +3,20 @@
|
|||
<div class="content-block" :class="specialClass">
|
||||
<div class="content-block__actions">
|
||||
<visibility-action
|
||||
v-if="!contentBlock.indent"
|
||||
:block="contentBlock"></visibility-action>
|
||||
<a @click="editContentBlock()" v-if="contentBlock.mine" class="content-block__action-button">
|
||||
<a @click="editContentBlock()" v-if="canEditContentBlock" class="content-block__action-button">
|
||||
<pen-icon class="content-block__action-icon action-icon"></pen-icon>
|
||||
</a>
|
||||
<a @click="deleteContentBlock(contentBlock.id)" v-if="contentBlock.mine" class="content-block__action-button">
|
||||
<a @click="deleteContentBlock(contentBlock.id)" v-if="canEditContentBlock" class="content-block__action-button">
|
||||
<trash-icon class="content-block__action-icon action-icon"></trash-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h3 v-if="instrumentLabel !== ''" class="content-block__instrument-label">{{instrumentLabel}}</h3>
|
||||
<h4 class="content-block__title">{{contentBlock.title}}</h4>
|
||||
<h4 class="content-block__title" v-if="!contentBlock.indent">{{contentBlock.title}}</h4>
|
||||
|
||||
<component v-for="component in contentBlock.contents"
|
||||
<component v-for="component in contentBlocksWithContentLists.contents"
|
||||
:key="component.id"
|
||||
:is="component.type"
|
||||
v-bind="component">
|
||||
|
|
@ -23,7 +24,7 @@
|
|||
|
||||
</div>
|
||||
|
||||
<add-content-block-button :after="contentBlock.id"></add-content-block-button>
|
||||
<add-content-block-button :after="contentBlock.id" v-if="!contentBlock.indent"></add-content-block-button>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -41,6 +42,7 @@
|
|||
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 Solution from '@/components/content-blocks/Solution';
|
||||
import AddContentBlockButton from '@/components/AddContentBlockButton';
|
||||
|
|
@ -59,6 +61,7 @@
|
|||
|
||||
export default {
|
||||
props: ['contentBlock', 'parent'],
|
||||
name: 'content-block',
|
||||
|
||||
components: {
|
||||
'text_block': TextBlock,
|
||||
|
|
@ -72,6 +75,7 @@
|
|||
'infogram_block': InfogramBlock,
|
||||
'genially_block': GeniallyBlock,
|
||||
'subtitle': SubtitleBlock,
|
||||
'content_list': ContentListBlock,
|
||||
Solution,
|
||||
Assignment,
|
||||
Task,
|
||||
|
|
@ -93,6 +97,59 @@
|
|||
}
|
||||
|
||||
return `Instrument - ${instruments[contentType]}`
|
||||
},
|
||||
canEditContentBlock() {
|
||||
return this.contentBlock.mine && !this.contentBlock.indent;
|
||||
},
|
||||
contentBlocksWithContentLists() {
|
||||
/*
|
||||
collects all conent_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 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
|
||||
return [...newContents, ...this.createContentListOrBlocks(contentList)];
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -125,9 +182,20 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
createContentListOrBlocks(contentList) {
|
||||
// if list contains only one item, return blocks
|
||||
if (contentList.length === 1) {
|
||||
return contentList[0].value;
|
||||
}
|
||||
|
||||
return [{
|
||||
type: 'content_list',
|
||||
contents: contentList,
|
||||
id: contentList[0].id
|
||||
}];
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showVisibility: false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div class="content-list-block__container">
|
||||
<div class="content-list-wrapper">
|
||||
<ol class="content-list">
|
||||
<li class="content-list__item contentlist-item" :key="contentBlock.id" v-for="contentBlock in contentBlocks">
|
||||
<content-block :contentBlock="contentBlock"></content-block>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import ContentBlock from '@/components/ContentBlock';
|
||||
|
||||
export default {
|
||||
props: ['contents', 'parent'],
|
||||
name: 'content-block-list',
|
||||
|
||||
components: {
|
||||
// https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
|
||||
ContentBlock: () => import('@/components/ContentBlock')
|
||||
},
|
||||
|
||||
computed: {
|
||||
contentBlocks() {
|
||||
return this.contents.map(contentBlock => {
|
||||
return Object.assign({}, contentBlock, {
|
||||
contents: [...contentBlock.value],
|
||||
indent: true
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.content-list-wrapper {
|
||||
.content-list {
|
||||
/* https://stackoverflow.com/questions/1632005/ordered-list-html-lower-alpha-with-right-parentheses */
|
||||
counter-reset: list;
|
||||
|
||||
&__item {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0 2*15px;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
font-weight: 600;
|
||||
left: 0;
|
||||
color: $color-brand;
|
||||
content: counter(list, lower-alpha) ") ";
|
||||
counter-increment: list;
|
||||
line-height: 27px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
.subtitle {
|
||||
padding-top: 1px;
|
||||
margin-bottom: $large-spacing;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -18,59 +18,67 @@ class GenericStreamFieldType(Scalar):
|
|||
@staticmethod
|
||||
def serialize(stream_value):
|
||||
stream_data = stream_value.stream_data
|
||||
|
||||
for d in stream_data:
|
||||
if isinstance(d, dict):
|
||||
_type = d['type']
|
||||
if _type == 'image_block':
|
||||
_value = d['value']
|
||||
value = {
|
||||
# 'value': _value,
|
||||
# 'id': d['id'],
|
||||
'path': Image.objects.get(id=_value).file.url
|
||||
}
|
||||
d['value'] = value
|
||||
if _type == 'assignment':
|
||||
_value = d['value']
|
||||
assignment_id = _value['assignment_id']
|
||||
try:
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
value = {
|
||||
'title': assignment.title,
|
||||
'assignment': assignment.assignment,
|
||||
'id': to_global_id('AssignmentNode', assignment.pk)
|
||||
}
|
||||
d['value'] = value
|
||||
except Assignment.DoesNotExist:
|
||||
logger.error('Assignment {} does not exist'.format(assignment_id))
|
||||
if _type == 'basic_knowledge' or _type == 'instrument':
|
||||
_value = d['value']
|
||||
basic_knowledge = BasicKnowledge.objects.get(pk=_value['basic_knowledge'])
|
||||
_value.update({
|
||||
'slug': basic_knowledge.slug
|
||||
})
|
||||
d['value'] = _value
|
||||
|
||||
# value = dict(d['value'])
|
||||
# if 'document' in value:
|
||||
# value['document'] = Document.objects.get(id=value['document']).file.url
|
||||
# if 'image' in value:
|
||||
# value['image'] = Image.objects.get(id=value['image']).file.url
|
||||
|
||||
# else:
|
||||
# _type = d[0]
|
||||
# value = dict(d[1])
|
||||
# if 'document' in value:
|
||||
# value['document'] = value['document'].file.url
|
||||
# if 'image' in value:
|
||||
# value['image'] = value['image'].file.url
|
||||
|
||||
return stream_data
|
||||
return augment_fields(stream_data)
|
||||
|
||||
# by_api = stream_value.stream_block.get_api_representation(stream_value)
|
||||
# return by_api
|
||||
|
||||
|
||||
def augment_fields(stream_data):
|
||||
for data in stream_data:
|
||||
if isinstance(data, dict):
|
||||
_type = data['type']
|
||||
if _type == 'image_block':
|
||||
_value = data['value']
|
||||
value = {
|
||||
# 'value': _value,
|
||||
# 'id': d['id'],
|
||||
'path': Image.objects.get(id=_value).file.url
|
||||
}
|
||||
data['value'] = value
|
||||
if _type == 'assignment':
|
||||
_value = data['value']
|
||||
assignment_id = _value['assignment_id']
|
||||
try:
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
value = {
|
||||
'title': assignment.title,
|
||||
'assignment': assignment.assignment,
|
||||
'id': to_global_id('AssignmentNode', assignment.pk)
|
||||
}
|
||||
data['value'] = value
|
||||
except Assignment.DoesNotExist:
|
||||
logger.error('Assignment {} does not exist'.format(assignment_id))
|
||||
if _type == 'basic_knowledge' or _type == 'instrument':
|
||||
_value = data['value']
|
||||
basic_knowledge = BasicKnowledge.objects.get(pk=_value['basic_knowledge'])
|
||||
_value.update({
|
||||
'slug': basic_knowledge.slug
|
||||
})
|
||||
data['value'] = _value
|
||||
|
||||
# value = dict(d['value'])
|
||||
# if 'document' in value:
|
||||
# value['document'] = Document.objects.get(id=value['document']).file.url
|
||||
# if 'image' in value:
|
||||
# value['image'] = Image.objects.get(id=value['image']).file.url
|
||||
|
||||
# else:
|
||||
# _type = d[0]
|
||||
# value = dict(d[1])
|
||||
# if 'document' in value:
|
||||
# value['document'] = value['document'].file.url
|
||||
# if 'image' in value:
|
||||
# value['image'] = value['image'].file.url
|
||||
|
||||
if _type == 'content_list_item':
|
||||
item_data = data['value']
|
||||
data['value'] = augment_fields(item_data)
|
||||
|
||||
return stream_data
|
||||
|
||||
|
||||
@convert_django_field.register(StreamField)
|
||||
def convert_stream_field(field, registry=None):
|
||||
return GenericStreamFieldType(description=field.help_text, required=not field.null)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import logging
|
|||
|
||||
from django.db import models
|
||||
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList, StreamFieldPanel
|
||||
from wagtail.core.blocks import StreamBlock
|
||||
from wagtail.core.fields import StreamField
|
||||
from wagtail.images.blocks import ImageChooserBlock
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ class ContentBlock(StrictHierarchyPage):
|
|||
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_content_blocks')
|
||||
user_created = models.BooleanField(default=False)
|
||||
|
||||
contents = StreamField([
|
||||
content_blocks = [
|
||||
('text_block', TextBlock()),
|
||||
('basic_knowledge', BasicKnowledgeBlock()),
|
||||
('assignment', AssignmentBlock()),
|
||||
|
|
@ -46,8 +47,11 @@ class ContentBlock(StrictHierarchyPage):
|
|||
('document_block', DocumentBlock()),
|
||||
('infogram_block', InfogramBlock()),
|
||||
('genially_block', GeniallyBlock()),
|
||||
('subtitle', SubtitleBlock()),
|
||||
], null=True, blank=True)
|
||||
('subtitle', SubtitleBlock())
|
||||
]
|
||||
|
||||
content_list_item = StreamBlock(content_blocks)
|
||||
contents = StreamField(content_blocks + [('content_list_item', content_list_item)], null=True, blank=True)
|
||||
|
||||
type = models.CharField(
|
||||
max_length=100,
|
||||
|
|
|
|||
Loading…
Reference in New Issue