Merge branch 'feature/image-upload'

This commit is contained in:
Daniel Egger 2018-09-19 18:03:30 +02:00
commit 5d52aef859
23 changed files with 222 additions and 24 deletions

4
.git-crypt/.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
# Do not edit this file. To specify the files to encrypt, create your own
# .gitattributes file in the directory where your files are.
* !filter !diff
*.gpg binary

View File

@ -0,0 +1,4 @@
<01>™'G©<47>þ£ n”~DùÎMÌHàŠ$L¥)kš~lƒ"i-Y%<25>7ß2%
1*&·Šš¤ç¥X—ò:ãÂn •2pRò4™zh×(sË£ÃR±ú"<22>k|¹:iN{Ýcˆòýó[,>‹ö’¼?šÝ³lÛ1%%än¾È§ïÓxاô¨K„û´¯%õ¤K#öÔ8$Úïo 1LsЧŸӹ¢†²QÉ`Šé h§*(LËóp#õ­Èz<C388>á1º¢ <0A> ešV°e8ÞõM¨pç$ðc2)(‰°1ú&«»lþÒ<Ù
<î±ëW¢•q¶Tƒ÷6÷y0CÁIjyÜFûþO<C3BE>žñZ”ŒÇ;ðïNoIŠÚ•†™ç?ßf Ä­¦Áf(g&#a^d¥-ä!ÄTDöŠ2&®Þ•@9{OÁnjšÔyø%WšƒÛ Ãñá(S´ó¬Å&Цì f<>ûÈÇ+ø¡(žìõdRú/Ž>â¡Â1ÅÆ<>òtÀkëL¾2´ÛÒÀ)¨÷_ ÈرF˜×û<C397>Êa<1B>寅—è:OJ¿ÿøªÔõ§Â_EfžÍƒ1t[¡Ô—Ý:L£=¨R5•o±Vôp—45
ïÝx<EFBFBD>¯”N7oÇœc•ànIŸ×,ƒ4ÝÍ—¤XUÙš˜üËçHy¢¤v>YÜ¢›Ó¿ý‘¢v Sƺúöû@ÐøýŠ&óë”?B2pH<70>ã=¯€ñÜöüûw'læŠïŽÀ툭Æï$f àaOiLGÛv`t

View File

@ -27,6 +27,7 @@ module.exports = {
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'indent': 'off',
'semi': 0,
'space-before-function-paren': 'off'
'space-before-function-paren': 'off',
'comma-dangle': 'off',
}
}
};

View File

@ -8,6 +8,21 @@
<link href='https://fonts.googleapis.com/css?family=Material+Icons' rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500,800" rel="stylesheet">
<link href="https://use.typekit.net/tck7ptw.css" rel="stylesheet">
<script>
window.UPLOADCARE_LOCALE = 'de';
window.UPLOADCARE_LOCALE_TRANSLATIONS = {
dialog: {
tabs: {
file: {
drag: 'Ziehen Sie ein Bild hier hinein',
button: 'Wählen Sie ein lokales Bild',
}
}
}
};
</script>
</head>
<body>
<div id="app">

View File

@ -36,6 +36,7 @@
import BasicKnowledgeWidget from '@/components/content-blocks/BasicKnowledgeWidget';
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';
@ -53,6 +54,7 @@
'basic_knowledge': BasicKnowledgeWidget,
'student_entry': StudentEntry,
'image_block': ImageBlock,
'image_url_block': ImageUrlBlock,
'video_block': VideoBlock,
'link_block': LinkBlock,
'document_block': DocumentBlock,

View File

@ -1,12 +1,12 @@
<template>
<modal>
<content-block-title-input slot="header" v-on:update-title="updateTitle" :title="contentBlock.title"
<content-block-title-input slot="header" v-on:update-title="updateTitle" :title="localContentBlock.title"
:error="error"></content-block-title-input>
<add-content-element class="content-block-form__add"
v-on:add-element="addElement"
:index="-1"
></add-content-element>
<div v-for="(element, index) in contentBlock.contents" :key="index" class="content-block-form__element">
<div v-for="(element, index) in localContentBlock.contents" :key="index" class="content-block-form__element">
<component
class="content-block-form__element-component"
:is="type(element)"
@ -17,6 +17,7 @@
v-on:link-change-text="changeLinkText"
v-on:text-change-value="changeTextValue"
v-on:document-change-url="changeDocumentUrl"
v-on:image-change-url="changeImageUrl"
v-on:video-change-url="changeVideoUrl">
</component>
<a class="content-block-form__remove" v-on:click="removeElement(index)">
@ -70,7 +71,7 @@
data() {
return {
error: false,
localContentBlock: Object.assign({}, this.contentBlock)
localContentBlock: JSON.parse(JSON.stringify(this.contentBlock))
}
},
@ -81,7 +82,7 @@
return 'link-form';
case 'video_block':
return 'video-form';
case 'image_block':
case 'image_url_block':
return 'image-form';
case 'text_block':
return 'text-form';
@ -112,6 +113,9 @@
changeVideoUrl(value, index) {
this._updateProperty(value, index, 'url')
},
changeImageUrl(value, index) {
this._updateProperty(value, index, 'url')
},
changeDocumentUrl(value, index) {
this._updateProperty(value, index, 'url')
},
@ -167,6 +171,14 @@
}
};
break;
case 'image_url_block':
el = {
...el,
value: {
url: ''
}
};
break;
}
this.localContentBlock.contents.splice(index, 1, el);

View File

@ -0,0 +1,19 @@
<template>
<img :src="value.url" alt="" class="image-block">
</template>
<script>
export default {
props: ['value']
}
</script>
<style scoped lang="scss">
.image-block {
width: 100%;
max-width: 100%;
border-radius: 13px;
margin-bottom: 30px;
}
</style>

View File

@ -8,7 +8,7 @@
<video-icon class="content-block-element-chooser-widget__link-icon"></video-icon>
<div class="content-block-element-chooser-widget__link-title">Video</div>
</div>
<div class="content-block-element-chooser-widget__link" v-on:click="$emit('change-type', index, 'image_block')">
<div class="content-block-element-chooser-widget__link" v-on:click="$emit('change-type', index, 'image_url_block')">
<image-icon class="content-block-element-chooser-widget__link-icon"></image-icon>
<div class="content-block-element-chooser-widget__link-title">Bild</div>
</div>

View File

@ -1,11 +1,56 @@
<template>
<div class="image-form">
<input id="image-input" type="file" class="image-form__file-input">
<label class="image-form__button" for="image-input">Bild vom Computer hochladen</label>
<div v-if="!value.url" ref="uploadcare-panel"></div>
<div v-if="value.url">
<img :src="previewUrl">
</div>
</div>
</template>
<script>
import uploadcare from 'uploadcare-widget';
export default {
props: ['value', 'index'],
mounted() {
let uploadcarePanel = openImagePanel(this.$refs['uploadcare-panel']);
uploadcarePanel.done(panelResult => {
console.log(panelResult);
panelResult.done(fileInfo => {
console.log(fileInfo);
this.$emit('link-change-url', fileInfo.cdnUrl, this.index)
});
panelResult.progress(p => {
console.log(p);
});
panelResult.fail(uploadResult => {
console.log('fail');
console.log(uploadResult);
});
});
function openImagePanel(panelElement) {
return uploadcare.openPanel(panelElement, null, {
tabs: ['file'],
publicKey: '78212ff39934a59775ac',
});
}
},
computed: {
previewUrl: function() {
console.log(this.value);
if (this.value && this.value.url) {
return this.value.url + '-/preview/200x200/';
}
return null;
}
},
}
</script>
<style scoped lang="scss">

View File

@ -0,0 +1,31 @@
.uploadcare--panel {
background: $color-lightgrey;
height: 200px;
border: none;
border-radius: 0;
}
.uploadcare--menu {
display: none;
}
.uploadcare--button {
background: none !important;
color: inherit;
border: none;
padding: 0 !important;
font: inherit;
border-bottom: 1px solid #444;
cursor: pointer;
border-radius: 0;
}
.uploadcare--button_primary:hover {
background: inherit;
border-color: inherit;
color: inherit;
}
.uploadcare--text_size_extra-large {
font-size: 24px;
}

View File

@ -8,9 +8,5 @@
@import "variables";
@import "buttons";
@import "forms";
@import "uploadcare_overwrite";
@import "help-text";

2
docs/secrets/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
* filter=git-crypt diff=git-crypt
.gitattributes !filter !diff

BIN
docs/secrets/passwords.md Normal file

Binary file not shown.

13
package-lock.json generated
View File

@ -35,6 +35,11 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
},
"jquery": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg=="
},
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
@ -44,6 +49,14 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-3.1.1.tgz",
"integrity": "sha512-syDl3htvM56w0HC0PTVA5jEEknOCJ3dWgWGDuaEtQUno8ORDCfZQbm12RzfWO3AC3YhWDoP61dlgmo8Z05Y97g=="
},
"uploadcare-widget": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/uploadcare-widget/-/uploadcare-widget-3.6.0.tgz",
"integrity": "sha512-QWvZtPG35ndZJfNxKSu+rrzgIPK+UhO4KzWntP9zfJBvaT1xdbIIF/OMYBhNFG5dbw5j7qFIS2SJvtZZAK9rfw==",
"requires": {
"jquery": ">=1.10.2"
}
}
}
}

View File

@ -10,6 +10,7 @@
},
"dependencies": {
"babel-polyfill": "^6.26.0",
"unfetch": "^3.0.0"
"unfetch": "^3.0.0",
"uploadcare-widget": "3.6.0"
}
}

View File

@ -0,0 +1,20 @@
# Generated by Django 2.0.6 on 2018-09-18 16:05
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
class Migration(migrations.Migration):
dependencies = [
('book', '0002_auto_20180913_0738'),
]
operations = [
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='doc-full')), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock()), ('url', wagtail.core.blocks.URLBlock())], icon='placeholder')), ('student_entry', wagtail.core.blocks.StructBlock([('task_text', wagtail.core.blocks.RichTextBlock())], icon='download')), ('image_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.RichTextBlock()), ('url', wagtail.core.blocks.URLBlock())], icon='image')), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())], icon='link')), ('task', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())], icon='media')), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())], icon='doc-full'))], blank=True, null=True),
),
]

View File

@ -5,7 +5,8 @@ from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList,
from wagtail.core.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock
from book.blocks import TextBlock, BasicKnowledgeBlock, StudentEntryBlock, LinkBlock, VideoBlock, DocumentBlock
from book.blocks import TextBlock, BasicKnowledgeBlock, StudentEntryBlock, LinkBlock, VideoBlock, DocumentBlock, \
ImageUrlBlock
from core.wagtail_utils import StrictHierarchyPage
from user.models import UserGroup
@ -36,6 +37,7 @@ class ContentBlock(StrictHierarchyPage):
('basic_knowledge', BasicKnowledgeBlock(icon='placeholder')),
('student_entry', StudentEntryBlock(icon='download')),
('image_block', ImageChooserBlock(icon='image')),
('image_url_block', ImageUrlBlock(icon='image')),
('link_block', LinkBlock(icon='link')),
('task', TextBlock(icon='tick')),
('video_block', VideoBlock(icon='media')),

View File

@ -7,6 +7,7 @@ class InputTypes(graphene.Enum):
# basic_knowledge = 'basic_knowledge' # probably won't be using this over the API
student_entry = 'student_entry'
image_block = 'image_block'
image_url_block = 'image_url_block'
link_block = 'link_block'
task = 'task'
video_block = 'video_block'

View File

@ -25,8 +25,12 @@ def handle_content_blocks(content_data):
}})
elif content['type'] == 'student_entry':
pass
elif content['type'] == 'image_block':
pass
elif content['type'] == 'image_url_block':
new_contents.append({
'type': 'image_url_block',
'value': {
'url': bleach.clean(content['value']['url'])
}})
elif content['type'] == 'link_block':
new_contents.append({
'type': 'link_block',

View File

@ -1,8 +1,4 @@
import io
import os
from django.test import TestCase
from graphene.test import Client
from graphql_relay import to_global_id
@ -11,8 +7,6 @@ from api.utils import get_graphql_mutation, get_object
from book.factories import ContentBlockFactory
from book.models import ContentBlock
from django.conf import settings
class NewContentBlockMutationTest(TestCase):
def setUp(self):
@ -52,3 +46,35 @@ class NewContentBlockMutationTest(TestCase):
content_block_page = get_object(ContentBlock, id)
self.assertEqual(content_block_page.title, title)
def test_addNewContentBlock_withImageUrlBlock(self):
self.assertEqual(ContentBlock.objects.count(), 1)
client = Client(schema=schema)
mutation = get_graphql_mutation('addContentBlock.gql')
title = "Hello World"
result = client.execute(mutation, variables={
'input': {
"contentBlock": {
"title": title,
"contents": [
{
"type": "image_url_block",
'value': {
"url": "/test.png"
}
}
]
},
"after": self.sibling_id
}
})
self.assertIsNone(result.get('errors'))
self.assertEqual(ContentBlock.objects.count(), 2)
new_content_block = result.get('data').get('addContentBlock').get('newContentBlock')
self.assertEqual(new_content_block.get('title'), title)
self.assertEqual(len(new_content_block['contents']), 1)
self.assertEqual(new_content_block['contents'], [{'type': 'image_url_block', 'value': {'url': '/test.png'}}])