LIL-190 - Взаимодействие с API при редактировании курса/уроков

remotes/origin/hasaccess
Vitaly Baev 8 years ago
parent f897296374
commit 8d69cfa7de
  1. 4
      project/templates/lilcity/index.html
  2. 5
      web/package-lock.json
  3. 1
      web/package.json
  4. 578
      web/src/components/CourseRedactor.vue
  5. 122
      web/src/components/LessonRedactor.vue
  6. 121
      web/src/components/blocks/BlockAdd.vue
  7. 16
      web/src/components/blocks/BlockImage.vue
  8. 16
      web/src/components/blocks/BlockImageText.vue
  9. 18
      web/src/components/blocks/BlockImages.vue
  10. 30
      web/src/components/blocks/BlockText.vue
  11. 16
      web/src/components/blocks/BlockVideo.vue
  12. 5
      web/src/components/objects/objects.js
  13. 10
      web/src/components/redactor/VueRedactor.vue
  14. 266
      web/src/js/modules/api.js
  15. 1
      web/src/js/modules/comments.js
  16. 29
      web/src/sass/_common.sass
  17. 1
      web/src/sass/app.sass

@ -32,7 +32,7 @@
<meta property="og:site_name" content="Site Name, i.e. Moz"> <meta property="og:site_name" content="Site Name, i.e. Moz">
<meta property="fb:admins" content="Facebook numeric ID"> <meta property="fb:admins" content="Facebook numeric ID">
<meta name="csrf-token" content="{{ csrf_token }}"> <meta name="csrf-token" content="{{ csrf_token }}">
<link rel="stylesheet" media="all" href={% static "css/app.css" %}> <link rel="stylesheet" media="all" href={% static "app.css" %}>
<link rel="shortcut icon" type="image/png" href="{% static 'img/favicon.ico' %}"/> <link rel="shortcut icon" type="image/png" href="{% static 'img/favicon.ico' %}"/>
<script> <script>
var viewportmeta = document.querySelector('meta[name="viewport"]'); var viewportmeta = document.querySelector('meta[name="viewport"]');
@ -480,7 +480,7 @@
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript" src={% static "js/app.js" %}></script> <script type="text/javascript" src={% static "app.js" %}></script>
{% block foot %}{% endblock foot %} {% block foot %}{% endblock foot %}
</body> </body>

@ -10127,6 +10127,11 @@
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
"dev": true "dev": true
}, },
"slugify": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.2.9.tgz",
"integrity": "sha512-n0cdJ+kN3slJu8SbZXt/EHjljBqF6MxvMGSg/NPpBzoY7yyXoH38wp/ox20a1JaG1KgmdTN5Lf3aS9+xB2Y2aQ=="
},
"smooth-scroll": { "smooth-scroll": {
"version": "12.1.5", "version": "12.1.5",
"resolved": "https://registry.npmjs.org/smooth-scroll/-/smooth-scroll-12.1.5.tgz", "resolved": "https://registry.npmjs.org/smooth-scroll/-/smooth-scroll-12.1.5.tgz",

@ -59,6 +59,7 @@
"jquery": "^3.3.1", "jquery": "^3.3.1",
"moment": "^2.20.1", "moment": "^2.20.1",
"owl.carousel": "^2.2.0", "owl.carousel": "^2.2.0",
"slugify": "^1.2.9",
"smooth-scroll": "^12.1.5", "smooth-scroll": "^12.1.5",
"validator": "^9.2.0", "validator": "^9.2.0",
"vue": "^2.5.13", "vue": "^2.5.13",

@ -1,222 +1,225 @@
<template> <template>
<form @submit.prevent="onSubmit"> <div>
<div class="info"> <div v-if="!courseLoading">
<div class="info__section" :style="backgroundStyle"> <form v-if="viewSection !== 'lessons-edit'" @submit.prevent="onSubmit">
<div class="info__main"> <div class="info">
<div class="info__head"> <div class="info__section" :style="coverBackgroundStyle">
<div class="info__user"> <div class="info__main">
<div class="info__ava ava"> <div class="info__head">
<img :src="authorPicture" alt="Аватар" class="ava__pic"> <div class="info__user">
</div> <div class="info__ava ava">
<div class="info__group"> <img :src="authorPicture" alt="Аватар" class="ava__pic">
<div class="info__label">АВТОР</div> </div>
<div class="info__value">{{ authorName }}</div> <div class="info__group">
<div class="info__label">АВТОР</div>
<div class="info__value">{{ authorName }}</div>
</div>
</div>
<div class="info__upload upload">
Загрузить фон
<input type="file" class="upload__file" @change="onCoverImageSelected">
</div>
</div> </div>
</div> <div class="info__title">
<div class="info__upload upload"> <div class="info__field field field_info">
Загрузить фон <div class="field__label">НАЗВАНИЕ КУРСА</div>
<input type="file" class="upload__file" @change="onBackgroundImageSelected"> <div class="field__wrap">
</div>
</div>
<div class="info__title">
<div class="info__field field field_info">
<div class="field__label">НАЗВАНИЕ КУРСА</div>
<div class="field__wrap">
<textarea class="field__textarea field__textarea_lg" title="Название курса" <textarea class="field__textarea field__textarea_lg" title="Название курса"
v-model="course.title"></textarea> v-model="course.title"></textarea>
</div>
</div>
</div> </div>
</div> <div class="info__foot">
</div> <div class="info__field field field_info">
<div class="info__foot"> <div class="field__label field__label_gray">КАТЕГОРИЯ</div>
<div class="info__field field field_info"> <div class="field__wrap">
<div class="field__label field__label_gray">КАТЕГОРИЯ</div> <lil-select :value.sync="categorySelect" :options="categoryOptions"
<div class="field__wrap"> placeholder="Выберите категорию"/>
<lil-select :value.sync="categorySelect" :options="categoryOptions" placeholder="Выберите категорию"/> </div>
</div> </div>
</div> <div class="info__field field field_info">
<div class="info__field field field_info"> <div class="field__label field__label_gray">ПРОДОЛЖИТЕЛЬНОСТЬ</div>
<div class="field__label field__label_gray">ПРОДОЛЖИТЕЛЬНОСТЬ</div> <div class="field__wrap">
<div class="field__wrap"> <input type="text" class="field__input" v-model.number="course.duration">
<input type="text" class="field__input" v-model.number="course.duration"> </div>
</div> </div>
</div> <div class="info__field field field_info">
<div class="info__field field field_info"> <div class="field__label field__label_gray">СТОИМОСТЬ</div>
<div class="field__label field__label_gray">СТОИМОСТЬ</div> <div class="field__wrap">
<div class="field__wrap"> <input type="text" class="field__input" v-model="displayPrice" :disabled="!course.is_paid">
<input type="text" class="field__input" v-model="displayPrice" :disabled="!course.is_paid"> </div>
</div> </div>
</div>
</div>
</div>
</div>
<div class="info__sidebar">
<div class="info__wrap">
<div class="info__fieldset">
<div class="info__field field">
<div class="field__label field__label_gray">ССЫЛКА</div>
<div class="field__wrap">
<link-input prefix="https://lil.city/course/" :url.sync="course.url"/>
</div>
</div>
<div class="info__field field">
<div class="field__label field__label_gray">ДОСТУП</div>
<div class="field__wrap">
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="true" class="switch__input" v-model="course.is_paid">
<span class="switch__content">Платный</span>
</label>
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="false" class="switch__input" v-model="course.is_paid">
<span class="switch__content">Бесплатный</span>
</label>
</div> </div>
</div> </div>
<label class="info__switch switch switch_lg">
<input type="checkbox" class="switch__input" v-model="course.is_featured">
<span class="switch__content">Выделить</span>
</label>
</div> </div>
<div class="info__fieldset"> <div class="info__sidebar">
<div class="info__field field"> <div class="info__wrap">
<div class="field__label field__label_gray">ЗАПУСК</div> <div class="info__fieldset">
<div class="field__wrap"> <!--<div class="info__field field">
<label class="field__switch switch switch_lg switch_circle"> <div class="field__label field__label_gray">ССЫЛКА</div>
<input type="radio" :value="false" class="switch__input" v-model="course.is_deferred"> <div class="field__wrap">
<span class="switch__content">Мгновенный</span> <input type="text" class="field__input" v-model="course.url">
</label> </div>
<label class="field__switch switch switch_lg switch_circle"> <div class="field__wrap field__wrap&#45;&#45;additional">{{ courseFullUrl }}</div>
<input type="radio" :value="true" class="switch__input" v-model="course.is_deferred"> </div>-->
<span class="switch__content">Отложенный</span> <div class="info__field field">
<div class="field__label field__label_gray">ДОСТУП</div>
<div class="field__wrap">
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="true" class="switch__input" v-model="course.is_paid">
<span class="switch__content">Платный</span>
</label>
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="false" class="switch__input" v-model="course.is_paid">
<span class="switch__content">Бесплатный</span>
</label>
</div>
</div>
<label class="info__switch switch switch_lg">
<input type="checkbox" class="switch__input" v-model="course.is_featured">
<span class="switch__content">Выделить</span>
</label> </label>
</div> </div>
</div> <div class="info__fieldset">
<div class="info__field field"> <div class="info__field field">
<div class="field__label">ДАТА</div> <div class="field__label field__label_gray">ЗАПУСК</div>
<div class="field__wrap"> <div class="field__wrap">
<vue-datepicker input-class="field__input" v-model="course.date" language="ru" format="dd/MM/yyyy"/> <label class="field__switch switch switch_lg switch_circle">
</div> <input type="radio" :value="false" class="switch__input" v-model="course.is_deferred">
</div> <span class="switch__content">Мгновенный</span>
<div class="info__field field"> </label>
<div class="field__label">ВРЕМЯ</div> <label class="field__switch switch switch_lg switch_circle">
<div class="field__wrap"> <input type="radio" :value="true" class="switch__input" v-model="course.is_deferred">
<lil-select :value.sync="course.time" :options="timeOptions" placeholder="Выберите время"/> <span class="switch__content">Отложенный</span>
</label>
</div>
</div>
<div class="info__field field" v-show="course.is_deferred">
<div class="field__label">ДАТА</div>
<div class="field__wrap">
<vue-datepicker input-class="field__input" v-model="course.date" language="ru" format="dd/MM/yyyy"/>
</div>
</div>
<div class="info__field field" v-show="course.is_deferred">
<div class="field__label">ВРЕМЯ</div>
<div class="field__wrap">
<lil-select :value.sync="course.time" :options="timeOptions" placeholder="Выберите время"/>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="section">
</div> <div class="section__center center">
<div class="section"> <div class="kit">
<div class="section__center center"> <div class="kit__section">
<div class="kit"> <div class="kit__field field">
<div class="kit__section"> <div class="field__wrap">
<div class="kit__field field"> <input type="text" class="field__input" placeholder="Кратко о курсе"
<div class="field__wrap"> v-model="course.short_description">
<input type="text" class="field__input" placeholder="Кратко о курсе" v-model="course.short_description"> </div>
</div>
</div> </div>
</div> <div class="kit__nav">
</div> <button class="kit__btn btn btn_lg"
<div class="kit__nav"> v-bind:class="{ 'btn_stroke': viewSection === 'course', 'btn_gray': viewSection !== 'course' }"
<button class="kit__btn btn btn_lg btn_stroke" type="button" @click="viewSection = 'course'">Описание курса</button> type="button" @click="viewSection = 'course'">Описание
<button class="kit__btn btn btn_lg btn_gray" type="button" @click="viewSection = 'lessons'" :disabled="!course.id">Уроки</button> курса
</div> </button>
<div v-if="viewSection === 'course'" class="kit__body"> <button class="kit__btn btn btn_lg"
<div v-for="block in course.blocks"> v-bind:class="{ 'btn_stroke': viewSection === 'lessons', 'btn_gray': viewSection !== 'lessons' }"
<block-text v-if="block.type === 'text'" type="button"
:title.sync="block.data.title" @click="showLessons"
:text.sync="block.data.text" :disabled="!course.id">
:access-token="accessToken"/> Уроки
<block-image-text v-if="block.type === 'image-text'" </button>
</div>
<div v-if="viewSection === 'course'" class="kit__body">
<div v-for="(block, index) in course.content">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
:index="index"
:title.sync="block.data.title" :title.sync="block.data.title"
:text.sync="block.data.text" :text.sync="block.data.text"
:image-id.sync="block.data.image_id" :images.sync="block.data.images"
:image-url.sync="block.data.image_url" v-on:remove="onBlockRemoved"
:access-token="accessToken"/> :access-token="accessToken"/>
<block-image v-if="block.type === 'image'" <block-video v-if="block.type === 'video'"
:title.sync="block.data.title" :index="index"
:image-id.sync="block.data.image_id" :title.sync="block.data.title"
:image-url.sync="block.data.image_url" v-on:remove="onBlockRemoved"
:access-token="accessToken"/> :video-url.sync="block.data.video_url"/>
<block-images v-if="block.type === 'images'" </div>
:title.sync="block.data.title"
:text.sync="block.data.text" <block-add v-on:added="onBlockAdded"/>
:images.sync="block.data.images"
:access-token="accessToken"/> <div class="kit__foot">
<block-video v-if="block.type === 'video'" <button type="submit" class="kit__submit btn btn_md" v-bind:class="{ loading: courseSaving }">
:title.sync="block.data.title" Сохранить
:video-url.sync="block.data.video_url"/>
</div>
<div class="kit__section">
<div v-if="!is_adding_block" class="kit__add add">
<button type="button" class="add__toggle" @click="is_adding_block = true">
<span class="add__circle">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
</span>
<span class="add__title">Добавить блок</span>
</button>
</div>
<div v-if="is_adding_block" class="kit__add add open">
<button type="button" class="add__toggle" @click="is_adding_block = false">
<span class="add__circle">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
</span>
<span class="add__title">Добавить блок</span>
</button>
<div class="add__list">
<button class="add__btn" type="button" @click="addBlockText">
<svg class="icon icon-text">
<use xlink:href="/static/img/sprite.svg#icon-text"></use>
</svg>
</button>
<button class="add__btn" type="button" @click="addBlockImage">
<svg class="icon icon-image">
<use xlink:href="/static/img/sprite.svg#icon-image"></use>
</svg>
</button>
<button type="button" class="add__btn" @click="addBlockImageText">
<svg class="icon icon-image-text">
<use xlink:href="/static/img/sprite.svg#icon-image-text"></use>
</svg>
</button>
<button type="button" class="add__btn" @click="addBlockImages">
<svg class="icon icon-images">
<use xlink:href="/static/img/sprite.svg#icon-images"></use>
</svg>
</button>
<button type="button" class="add__btn" @click="addBlockVideo">
<svg class="icon icon-video-stroke">
<use xlink:href="/static/img/sprite.svg#icon-video-stroke"></use>
</svg>
</button> </button>
</div> </div>
</div> </div>
</div> <div v-if="viewSection === 'lessons'" class="kit__body">
<div class="kit__section"> <div class="lessons__title title">Содержание курса</div>
<div class="kit__theme">О чем курс?</div> <div v-if="!lessonsLoading" class="lessons__list">
<vue-redactor :value.sync="course.about" /> <div class="lessons__item" v-for="(lesson, index) in lessons">
</div> <div class="lessons__actions">
<div class="kit__foot"> <button type="button" class="lessons__action" @click="removeLesson(index)">
<button type="submit" class="kit__submit btn btn_md">Сохранить</button> <svg class="icon icon-delete">
</div> <use xlink:href="/static/img/sprite.svg#icon-delete"></use>
</div> </svg>
<div v-if="viewSection === 'lessons'" class="kit__body"> </button>
<div class="lessons__title title">Содержание курса</div> <button type="button" class="lessons__action" @click="editLesson(index)">
<div class="lessons__list"> <svg class="icon icon-edit">
<use xlink:href="/static/img/sprite.svg#icon-edit"></use>
</div> </svg>
<div class="lessons__foot"> </button>
<button class="lessons__btn btn btn_md">СОЗДАТЬ УРОК</button> </div>
<div class="lessons__subtitle subtitle">{{ lesson.title }}</div>
<div class="lessons__row">
<div class="lessons__content">{{ lesson.short_description }}</div>
</div>
</div>
</div>
<div v-if="lessonsLoading">Загрузка...</div>
<div class="lessons__foot">
<button type="button" class="lessons__btn btn btn_md" @click="addLesson">СОЗДАТЬ УРОК</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </form>
<form v-if="viewSection === 'lessons-edit'" @submit.prevent="onLessonSubmit">
<lesson-redactor :lesson.sync="currentLesson" :saving="lessonSaving" :access-token="accessToken"
v-on:back="goToLessons"/>
</form>
</div>
<div v-else>
<div>Loading...</div>
</div> </div>
</form> </div>
</template> </template>
<script> <script>
@ -227,13 +230,14 @@
import BlockImages from './blocks/BlockImages' import BlockImages from './blocks/BlockImages'
import BlockImageText from './blocks/BlockImageText' import BlockImageText from './blocks/BlockImageText'
import BlockVideo from './blocks/BlockVideo' import BlockVideo from './blocks/BlockVideo'
import VueRedactor from './redactor/VueRedactor'
import LilSelect from "./inputs/LilSelect"; import LilSelect from "./inputs/LilSelect";
import LessonRedactor from "./LessonRedactor";
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
import BlockAdd from "./blocks/BlockAdd";
export default { export default {
name: "course-redactor", name: "course-redactor",
props: ["authorName", "authorPicture", "accessToken"], props: ["authorName", "authorPicture", "accessToken", "courseId"],
data() { data() {
return { return {
viewSection: 'course', viewSection: 'course',
@ -243,20 +247,26 @@
categorySelect: null, categorySelect: null,
duration: 1, duration: 1,
price: 1000, price: 1000,
url: 'my-awesome-url', url: '',
backgroundImage: '', coverImage: '',
coverImageId: null,
is_paid: true, is_paid: true,
is_featured: true, is_featured: true,
is_deferred: false, is_deferred: false,
date: '2018-03-08', date: '2018-03-08',
time: null, time: null,
short_description: 'Этот курс поможет детям освоить базовые навыки рисования простых персонажей', short_description: 'Этот курс поможет детям освоить базовые навыки рисования простых персонажей',
blocks: [], content: [],
about: '<b>Главная цель курса</b> - рассказать...', gallery: {
}, images: [],
lessons: { }
}, },
courseLoading: false,
courseSaving: false,
lessons: [],
lessonsLoading: false,
lessonSaving: false,
currentLesson: null,
is_adding_block: false, is_adding_block: false,
timeOptions: [ timeOptions: [
{ {
@ -300,69 +310,27 @@
} }
}, },
methods: { methods: {
onBackgroundImageSelected(event) { onCoverImageSelected(event) {
let file = event.target.files[0]; let file = event.target.files[0];
let reader = new FileReader(); let reader = new FileReader();
reader.onload = () => { reader.onload = () => {
this.course.backgroundImage = reader.result; this.$set(this.course, 'coverImage', reader.result);
api.uploadImage(reader.result, this.accessToken)
.then((response) => {
this.course.coverImageId = response.data.id;
})
.catch((error) => {
console.log('error', error);
});
}; };
if (file) { if (file) {
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
}, },
onCoursePriceChange(event) { onCoursePriceChange(event) {
console.log('onCoursePriceChange');
this.course.price = event.target.value; this.course.price = event.target.value;
}, },
addBlockText() {
this.course.blocks.push({
type: 'text',
data: {
title: 'тест заголовок',
text: 'текст',
}
})
},
addBlockImage() {
this.course.blocks.push({
type: 'image',
data: {
title: 'тест картинка',
image_id: null,
image_url: null,
}
})
},
addBlockImageText() {
this.course.blocks.push({
type: 'image-text',
data: {
title: 'тест картинка-текст',
text: 'текст какой-то',
image_id: null,
image_url: null,
}
})
},
addBlockImages() {
this.course.blocks.push({
type: 'images',
data: {
title: 'тест заголовок картинок',
text: 'описание блока галереи',
images: [],
}
})
},
addBlockVideo() {
this.course.blocks.push({
type: 'video',
data: {
title: 'тест видео',
video_url: 'http://vimeo.com/safmklsamfk',
}
})
},
updateCategory() { updateCategory() {
if (this.categoryOptions && Array.isArray(this.categoryOptions) && this.course.category) { if (this.categoryOptions && Array.isArray(this.categoryOptions) && this.course.category) {
this.categoryOptions.forEach((category) => { this.categoryOptions.forEach((category) => {
@ -372,10 +340,92 @@
}); });
} }
}, },
onBlockRemoved(blockIndex) {
const blockToRemove = this.course.content[blockIndex];
// Удаляем блок из Vue
this.course.content.splice(blockIndex, 1);
// Если блок уже был записан в БД, отправляем запрос на сервер на удаление блока из БД
if (blockToRemove.data.id) {
api.removeContentBlock(blockToRemove, this.accessToken);
}
},
onBlockAdded(blockData) {
this.course.content.push(blockData);
},
removeLesson(lessonIndex) {
if (!confirm('Вы действительно хотите удалить этот урок?')) {
return
}
const lesson = this.lessons[lessonIndex];
if (lesson.hasOwnProperty('id') && lesson.id) {
api.removeCourseLesson(lesson.id, this.accessToken);
}
this.lessons.splice(lessonIndex, 1);
},
editLesson(lessonIndex) {
this.currentLesson = this.lessons[lessonIndex];
this.viewSection = 'lessons-edit';
},
showLessons() {
this.viewSection = 'lessons';
},
addLesson() {
this.currentLesson = {
title: '',
short_description: '',
course_id: this.course.id,
content: [],
};
this.viewSection = 'lessons-edit';
},
onSubmit() { onSubmit() {
this.courseSaving = true;
api.saveCourse(this.course, this.accessToken) api.saveCourse(this.course, this.accessToken)
.then((response) => { .then((response) => {
this.course = api.convertCourseJson(response.course); this.courseSaving = false;
this.course = api.convertCourseJson(response.data);
})
.catch((err) => {
this.courseSaving = false;
});
},
onLessonSubmit() {
this.lessonSaving = true;
const currentLessonId = this.currentLesson.id;
api.saveLesson(this.currentLesson, this.accessToken)
.then((response) => {
this.lessonSaving = false;
const newLesson = api.convertLessonJson(response.data);
newLesson.course_id = this.course.id;
this.currentLesson = newLesson;
if (!currentLessonId) {
this.lessons.push(newLesson);
}
if (this.lessons && Array.isArray(this.lessons)) {
this.lessons.forEach((lesson, index) => {
if (newLesson.id === lesson.id) {
this.$set('lessons', index, newLesson);
}
});
}
})
.catch((err) => {
this.lessonSaving = false;
});
},
goToLessons() {
this.viewSection = 'lessons';
},
loadCourse(courseId) {
this.courseLoading = true;
api.loadCourse(courseId, this.accessToken)
.then((response) => {
this.courseLoading = false;
this.course = api.convertCourseJson(response.data);
})
.catch((err) => {
this.courseLoading = false;
console.log('error course loading', err);
}); });
} }
}, },
@ -392,10 +442,14 @@
} }
this.updateCategory(); this.updateCategory();
}); });
if (this.courseId) {
this.loadCourse(this.courseId);
}
}, },
computed: { computed: {
backgroundStyle() { coverBackgroundStyle() {
return this.course.backgroundImage ? `background-image: url(${this.course.backgroundImage});` : ''; return this.course.coverImage ? `background-image: url(${this.course.coverImage});` : '';
}, },
displayPrice: { displayPrice: {
get: function () { get: function () {
@ -421,9 +475,15 @@
set(value) { set(value) {
this.course.category = value.value; this.course.category = value.value;
} }
},
courseFullUrl() {
let suffix = this.course.url ? this.course.url : 'ваша_ссылка';
return `https://lil.city/course/${suffix}`;
} }
}, },
components: { components: {
BlockAdd,
LessonRedactor,
LilSelect, LilSelect,
BlockText, BlockText,
'link-input': LinkInput, 'link-input': LinkInput,
@ -433,7 +493,7 @@
'block-image-text': BlockImageText, 'block-image-text': BlockImageText,
'block-images': BlockImages, 'block-images': BlockImages,
'block-video': BlockVideo, 'block-video': BlockVideo,
'vue-redactor': VueRedactor, 'lesson-redactor': LessonRedactor,
} }
} }
</script> </script>

@ -0,0 +1,122 @@
<template>
<div class="section">
<div class="section__center center">
<div class="kit">
<div class="kit__go go">
<a href="#" class="go__item" @click.prevent="goBack">
<div class="go__arrow">
<svg class="icon icon-arrow-left">
<use xlink:href="/static/img/sprite.svg#icon-arrow-left"></use>
</svg>
</div>
<div class="go__title">К списку уроков</div>
</a>
</div>
<div class="kit__title title">{{ title }}</div>
<div class="kit__section">
<div class="kit__field field">
<div class="field__wrap">
<input type="text" class="field__input" placeholder="Название урока" v-model="lesson.title">
</div>
</div>
<div class="kit__field field">
<div class="field__wrap">
<textarea class="field__input" placeholder="Описание урока" v-model="lesson.short_description"></textarea>
</div>
</div>
</div>
<div v-for="(block, index) in lesson.content">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
<block-add v-on:added="onBlockAdded" />
<div class="kit__foot">
<button class="kit__submit btn btn_md" v-bind:class="{ loading: saving }">Сохранить</button>
</div>
</div>
</div>
</div>
</template>
<script>
import BlockAdd from "./blocks/BlockAdd";
import BlockText from './blocks/BlockText'
import BlockImage from './blocks/BlockImage'
import BlockImages from './blocks/BlockImages'
import BlockImageText from './blocks/BlockImageText'
import BlockVideo from './blocks/BlockVideo'
import {api} from "../js/modules/api";
export default {
name: "lesson-redactor",
props: ["lesson", "saving", "accessToken"],
methods: {
goBack() {
this.$emit('back');
},
onBlockAdded(blockData) {
this.lesson.content.push(blockData);
this.$emit('update:lesson', this.lesson);
},
onBlockRemoved(blockIndex) {
const blockToRemove = this.lesson.content[blockIndex];
// Удаляем блок из Vue
this.lesson.content.splice(blockIndex, 1);
this.$emit('update:lesson', this.lesson);
// Если блок уже был записан в БД, отправляем запрос на сервер на удаление блока из БД
if (blockToRemove.data.id) {
api.removeContentBlock(blockToRemove, this.accessToken);
}
}
},
computed: {
title() {
return this.lesson && this.lesson.id ? 'Редактирование урока' : 'Создать урок';
}
},
components: {
BlockAdd,
'block-text': BlockText,
'block-image': BlockImage,
'block-image-text': BlockImageText,
'block-images': BlockImages,
'block-video': BlockVideo,
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,121 @@
<template>
<div class="kit__section">
<div v-if="!isOpen" class="kit__add add">
<button type="button" class="add__toggle" @click="isOpen = true">
<span class="add__circle">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
</span>
<span class="add__title">Добавить блок</span>
</button>
</div>
<div v-if="isOpen" class="kit__add add open">
<button type="button" class="add__toggle" @click="isOpen = false">
<span class="add__circle">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
</span>
<span class="add__title">Добавить блок</span>
</button>
<div class="add__list">
<button class="add__btn" type="button" @click="addBlockText">
<svg class="icon icon-text">
<use xlink:href="/static/img/sprite.svg#icon-text"></use>
</svg>
</button>
<button class="add__btn" type="button" @click="addBlockImage">
<svg class="icon icon-image">
<use xlink:href="/static/img/sprite.svg#icon-image"></use>
</svg>
</button>
<button type="button" class="add__btn" @click="addBlockImageText">
<svg class="icon icon-image-text">
<use xlink:href="/static/img/sprite.svg#icon-image-text"></use>
</svg>
</button>
<button type="button" class="add__btn" @click="addBlockImages">
<svg class="icon icon-images">
<use xlink:href="/static/img/sprite.svg#icon-images"></use>
</svg>
</button>
<button type="button" class="add__btn" @click="addBlockVideo">
<svg class="icon icon-video-stroke">
<use xlink:href="/static/img/sprite.svg#icon-video-stroke"></use>
</svg>
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "block-add",
data() {
return {
isOpen: false
}
},
methods: {
add(blockData) {
this.isOpen = false;
this.$emit('added', blockData)
},
addBlockText() {
this.add({
type: 'text',
data: {
title: 'тест заголовок',
text: 'текст',
}
})
},
addBlockImage() {
this.add({
type: 'image',
data: {
title: 'тест картинка',
image_id: null,
image_url: null,
}
})
},
addBlockImageText() {
this.add({
type: 'image-text',
data: {
title: 'тест картинка-текст',
text: 'текст какой-то',
image_id: null,
image_url: null,
}
})
},
addBlockImages() {
this.add({
type: 'images',
data: {
title: 'тест заголовок картинок',
text: 'описание блока галереи',
images: [],
}
})
},
addBlockVideo() {
this.add({
type: 'video',
data: {
title: 'тест видео',
video_url: 'http://vimeo.com/safmklsamfk',
}
})
},
}
}
</script>
<style scoped>
</style>

@ -1,7 +1,14 @@
<template> <template>
<div class="kit__section"> <div class="kit__section kit__section--block">
<div class="kit__section-remove">
<button type="button" @click="onRemove">
<svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use>
</svg>
</button>
</div>
<div class="kit__field field"> <div class="kit__field field">
<div class="field__wrap"> <div class="field__wrap field__wrap--title">
<input type="text" <input type="text"
:value="title" :value="title"
class="field__input" class="field__input"
@ -21,7 +28,7 @@
export default { export default {
name: "block-image", name: "block-image",
props: ["title", "imageUrl", "imageId", "accessToken"], props: ["index", "title", "imageUrl", "imageId", "accessToken"],
methods: { methods: {
onTitleChange(event) { onTitleChange(event) {
this.$emit('update:title', event.target.value); this.$emit('update:title', event.target.value);
@ -31,6 +38,9 @@
}, },
onUpdateImageId(newValue) { onUpdateImageId(newValue) {
this.$emit('update:imageId', newValue); this.$emit('update:imageId', newValue);
},
onRemove() {
this.$emit('remove', this.index);
} }
}, },
components: { components: {

@ -1,11 +1,18 @@
<template> <template>
<div class="kit__section"> <div class="kit__section kit__section--block">
<div class="kit__section-remove">
<button type="button" @click="onRemove">
<svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use>
</svg>
</button>
</div>
<div class="kit__row"> <div class="kit__row">
<lil-image :image-id="imageId" :image-url="imageUrl" v-on:update:imageUrl="onUpdateImageUrl" <lil-image :image-id="imageId" :image-url="imageUrl" v-on:update:imageUrl="onUpdateImageUrl"
v-on:update:imageId="onUpdateImageId" :access-token="accessToken"/> v-on:update:imageId="onUpdateImageId" :access-token="accessToken"/>
<div class="kit__fieldset"> <div class="kit__fieldset">
<div class="kit__field field"> <div class="kit__field field">
<div class="field__wrap"> <div class="field__wrap field__wrap--title">
<input type="text" <input type="text"
:value="title" :value="title"
class="field__input" class="field__input"
@ -31,7 +38,7 @@
export default { export default {
name: "block-image-text", name: "block-image-text",
props: ["title", "text", "imageUrl", "imageId", "accessToken"], props: ["index", "title", "text", "imageUrl", "imageId", "accessToken"],
methods: { methods: {
onTitleChange(event) { onTitleChange(event) {
this.$emit('update:title', event.target.value); this.$emit('update:title', event.target.value);
@ -44,6 +51,9 @@
}, },
onUpdateImageId(newValue) { onUpdateImageId(newValue) {
this.$emit('update:imageId', newValue); this.$emit('update:imageId', newValue);
},
onRemove() {
this.$emit('remove', this.index);
} }
}, },
components: { components: {

@ -1,7 +1,14 @@
<template> <template>
<div class="kit__section"> <div class="kit__section kit__section--block">
<div class="kit__section-remove">
<button type="button" @click="onRemove">
<svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use>
</svg>
</button>
</div>
<div class="kit__field field"> <div class="kit__field field">
<div class="field__wrap"> <div class="field__wrap field__wrap--title">
<input type="text" <input type="text"
:value="title" :value="title"
class="field__input" class="field__input"
@ -36,7 +43,7 @@
export default { export default {
name: "block-images", name: "block-images",
props: ["title", "text", "images", "accessToken"], props: ["index", "title", "text", "images", "accessToken"],
methods: { methods: {
onTitleChange(event) { onTitleChange(event) {
this.$emit('update:title', event.target.value); this.$emit('update:title', event.target.value);
@ -57,8 +64,6 @@
api.uploadImage(reader.result, this.accessToken) api.uploadImage(reader.result, this.accessToken)
.then((response) => { .then((response) => {
console.log('completed', response);
let images = this.images; let images = this.images;
images.forEach((image, index) => { images.forEach((image, index) => {
if (image.src === reader.result) { if (image.src === reader.result) {
@ -77,6 +82,9 @@
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
}); });
},
onRemove() {
this.$emit('remove', this.index);
} }
} }
} }

@ -1,7 +1,14 @@
<template> <template>
<div class="kit__section"> <div class="kit__section kit__section--block">
<div class="kit__section-remove">
<button type="button" @click="onRemove">
<svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use>
</svg>
</button>
</div>
<div class="kit__field field"> <div class="kit__field field">
<div class="field__wrap"> <div class="field__wrap field__wrap--title">
<input type="text" <input type="text"
:value="title" :value="title"
class="field__input" class="field__input"
@ -11,26 +18,31 @@
</div> </div>
<div class="kit__field field"> <div class="kit__field field">
<div class="field__wrap"> <div class="field__wrap">
<textarea class="field__textarea field__textarea_sm" <vue-redactor :value="text" v-on:update:value="onTextChange" placeholder="Описание"/>
:value="text"
placeholder="Описание"
@change="onTextChange"></textarea>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import VueRedactor from '../redactor/VueRedactor';
export default { export default {
name: "block-text", name: "block-text",
props: ["title", "text"], props: ["index", "title", "text"],
methods: { methods: {
onTitleChange(event) { onTitleChange(event) {
this.$emit('update:title', event.target.value); this.$emit('update:title', event.target.value);
}, },
onTextChange(event) { onTextChange(newValue) {
this.$emit('update:text', event.target.value); this.$emit('update:text', newValue);
},
onRemove() {
this.$emit('remove', this.index);
} }
},
components: {
'vue-redactor': VueRedactor,
} }
} }
</script> </script>

@ -1,7 +1,14 @@
<template> <template>
<div class="kit__section"> <div class="kit__section kit__section--block">
<div class="kit__section-remove">
<button type="button" @click="onRemove">
<svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use>
</svg>
</button>
</div>
<div class="kit__field field"> <div class="kit__field field">
<div class="field__wrap"> <div class="field__wrap field__wrap--title">
<input type="text" <input type="text"
:value="title" :value="title"
class="field__input" class="field__input"
@ -26,13 +33,16 @@
<script> <script>
export default { export default {
name: "block-video", name: "block-video",
props: ["title", "videoUrl"], props: ["index", "title", "videoUrl"],
methods: { methods: {
onTitleChange(event) { onTitleChange(event) {
this.$emit('update:title', event.target.value); this.$emit('update:title', event.target.value);
}, },
onVideoUrlChange(event) { onVideoUrlChange(event) {
this.$emit('update:videoUrl', event.target.value); this.$emit('update:videoUrl', event.target.value);
},
onRemove() {
this.$emit('remove', this.index);
} }
} }
} }

@ -0,0 +1,5 @@
export var lessonObject = {
title: '',
short_description: '',
course_id: 0
};

@ -7,7 +7,7 @@
import './redactor-full.js'; import './redactor-full.js';
export default { export default {
props: ['value'], props: ['value', 'placeholder'],
name: "vue-redactor", name: "vue-redactor",
data() { data() {
return { return {
@ -18,9 +18,9 @@
const me = this; const me = this;
$(me.$refs.input).redactor({ $(me.$refs.input).redactor({
air: true, air: true,
formatting: ['p', 'blockquote', 'h2', 'h3', 'h4'], buttonsHide: ['image', 'link', 'format'],
lang: 'ru', lang: 'ru',
placeholder: 'Напишите, о чем ваш курс', placeholder: this.placeholder,
callbacks: { callbacks: {
visual: function () { visual: function () {
me.$emit('update:value', this.code.get()); me.$emit('update:value', this.code.get());
@ -30,6 +30,10 @@
} }
}, },
}); });
},
beforeDestroy() {
$(this.$refs.input).redactor('core.destroy');
$(this.$refs.input).remove();
} }
} }
</script> </script>

@ -21,6 +21,13 @@ export const api = {
} }
}); });
}, },
loadCourse: (courseId, accessToken) => {
return axios.get(`/api/v1/courses/${courseId}/`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
saveCourse: (courseObject, accessToken) => { saveCourse: (courseObject, accessToken) => {
const isAdding = (!courseObject.hasOwnProperty('id') || !courseObject.hasOwnProperty('id')); const isAdding = (!courseObject.hasOwnProperty('id') || !courseObject.hasOwnProperty('id'));
@ -40,13 +47,17 @@ export const api = {
duration: courseObject.duration, duration: courseObject.duration,
is_featured: courseObject.is_featured, is_featured: courseObject.is_featured,
url: courseObject.url, url: courseObject.url,
cover: courseObject.cover_id ? courseObject.cover_id : null, cover: courseObject.coverImageId ? courseObject.coverImageId : null,
content: courseObject.blocks.map((block) => { gallery: {
gallery_images: courseObject.gallery && courseObject.gallery.images ? courseObject.gallery.images : []
},
content: courseObject.content.map((block, index) => {
if (block.type === 'text') { if (block.type === 'text') {
return { return {
'type': 'text', 'type': 'text',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'position': ++index,
'title': block.data.title, 'title': block.data.title,
'txt': block.data.text, 'txt': block.data.text,
} }
@ -56,6 +67,7 @@ export const api = {
'type': 'image', 'type': 'image',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'position': ++index,
'title': block.data.title, 'title': block.data.title,
'img': block.data.image_id, 'img': block.data.image_id,
} }
@ -65,6 +77,7 @@ export const api = {
'type': 'image-text', 'type': 'image-text',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'position': ++index,
'title': block.data.title, 'title': block.data.title,
'img': block.data.image_id, 'img': block.data.image_id,
'txt': block.data.text, 'txt': block.data.text,
@ -75,6 +88,7 @@ export const api = {
'type': 'images', 'type': 'images',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'position': ++index,
'title': block.data.title, 'title': block.data.title,
'images': block.data.images.map((galleryImage) => { 'images': block.data.images.map((galleryImage) => {
return { return {
@ -89,6 +103,7 @@ export const api = {
'type': 'video', 'type': 'video',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'position': ++index,
'title': block.data.title, 'title': block.data.title,
'url': block.data.video_url, 'url': block.data.video_url,
} }
@ -97,99 +112,189 @@ export const api = {
}), }),
}; };
console.log(courseJson);
let request;
if (isAdding) { if (isAdding) {
return api.addCourse(courseJson, accessToken); return api.addCourse(courseJson, accessToken);
} else { } else {
return api.updateCourse(courseObject.id, courseJson, accessToken); return api.updateCourse(courseObject.id, courseJson, accessToken);
} }
}, },
convertCourseJson: (courseJSON) => { saveLesson: (lessonObject, accessToken) => {
let isDeferred = false; const isAdding = (!lessonObject.hasOwnProperty('id') || !lessonObject.hasOwnProperty('id'));
let deferredDate = '';
let deferredTime = ''; const lessonJson = {
if (courseJSON.deferred_start_at) { title: lessonObject.title,
let deferredDateTime = moment(courseJSON.deferred_start_at); short_description: lessonObject.short_description,
isDeferred = true; course: lessonObject.course_id,
deferredDate = deferredDateTime.format('MM-DD-YYYY'); content: lessonObject.content.map((block, index) => {
deferredTime = deferredDateTime.format('HH:mm'); if (block.type === 'text') {
}
return {
id: courseJSON.id,
title: courseJSON.title,
short_description: courseJSON.short_description,
category: courseJSON.category,
price: courseJSON.price,
is_paid: courseJSON.price === 0,
is_deferred: isDeferred,
date: deferredDate,
time: deferredTime ? {title: deferredTime, value: deferredTime} : null,
duration: courseJSON.duration,
is_featured: courseJSON.is_featured,
url: courseJSON.url,
cover_id: courseJSON.cover.id,
cover_url: courseJSON.cover.image,
content: courseJSON.content.map((contentItem) => {
if (contentItem.type === 'text') {
return { return {
'type': 'text', 'type': 'text',
'data': { 'data': {
'id': contentItem.data.id ? contentItem.data.id : null, 'id': block.data.id ? block.data.id : null,
'title': contentItem.data.title, 'position': ++index,
'text': contentItem.data.txt, 'title': block.data.title,
'txt': block.data.text,
} }
} }
} else if (contentItem.type === 'image') { } else if (block.type === 'image') {
return { return {
'type': 'image', 'type': 'image',
'data': { 'data': {
'id': contentItem.data.id ? contentItem.data.id : null, 'id': block.data.id ? block.data.id : null,
'title': contentItem.data.title, 'position': ++index,
'image_id': contentItem.data.image.id, 'title': block.data.title,
'image_url': contentItem.data.image.image, 'img': block.data.image_id,
} }
} }
} else if (contentItem.type === 'image-text') { } else if (block.type === 'image-text') {
return { return {
'type': 'image-text', 'type': 'image-text',
'data': { 'data': {
'id': contentItem.data.id ? contentItem.data.id : null, 'id': block.data.id ? block.data.id : null,
'title': contentItem.data.title, 'position': ++index,
'image_id': contentItem.data.image.id, 'title': block.data.title,
'image_url': contentItem.data.image.image, 'img': block.data.image_id,
'text': contentItem.data.txt, 'txt': block.data.text,
} }
} }
} else if (contentItem.type === 'images') { } else if (block.type === 'images') {
return { return {
'type': 'images', 'type': 'images',
'data': { 'data': {
'id': contentItem.data.id ? contentItem.data.id : null, 'id': block.data.id ? block.data.id : null,
'title': contentItem.data.title, 'position': ++index,
'images': contentItem.data.images.map((galleryImage) => { 'title': block.data.title,
'images': block.data.images.map((galleryImage) => {
return { return {
'id': galleryImage.id ? galleryImage.id : null, 'id': galleryImage.id ? galleryImage.id : null,
'image_id': galleryImage.image.id, 'img': galleryImage.img,
'image_url': galleryImage.image.url,
} }
}), }),
} }
} }
} else if (contentItem.type === 'video') { } else if (block.type === 'video') {
return { return {
'type': 'video', 'type': 'video',
'data': { 'data': {
'id': contentItem.data.id ? contentItem.data.id : null, 'id': block.data.id ? block.data.id : null,
'title': contentItem.data.title, 'position': ++index,
'video_url': contentItem.data.url, 'title': block.data.title,
'url': block.data.video_url,
} }
} }
} }
}), }),
};
if (isAdding) {
return api.addLesson(lessonJson, accessToken);
} else {
return api.updateLesson(lessonObject.id, lessonJson, accessToken);
} }
}, },
convertLessonJson: (lessonJSON) => {
return {
id: lessonJSON.id,
title: lessonJSON.title,
short_description: lessonJSON.short_description,
content: api.convertContentResponse(lessonJSON.content)
}
},
convertCourseJson: (courseJSON) => {
let isDeferred = false;
let deferredDate = '';
let deferredTime = '';
if (courseJSON.deferred_start_at) {
let deferredDateTime = moment(courseJSON.deferred_start_at);
isDeferred = true;
deferredDate = deferredDateTime.format('MM-DD-YYYY');
deferredTime = deferredDateTime.format('HH:mm');
}
return {
id: courseJSON.id,
title: courseJSON.title,
short_description: courseJSON.short_description,
category: courseJSON.category.id ? courseJSON.category.id : courseJSON.category,
price: parseFloat(courseJSON.price),
is_paid: parseFloat(courseJSON.price) === 0,
is_deferred: isDeferred,
date: deferredDate,
time: deferredTime ? {title: deferredTime, value: deferredTime} : null,
duration: courseJSON.duration,
is_featured: courseJSON.is_featured,
url: courseJSON.url,
coverImageId: courseJSON.cover && courseJSON.cover.id ? courseJSON.cover.id : null,
coverImage: courseJSON.cover && courseJSON.cover.image ? courseJSON.cover.image : null,
content: api.convertContentResponse(courseJSON.content),
}
},
convertContentResponse: (contentJson) => {
return contentJson.sort((a, b) => {
if (a.position < b.position) {
return -1;
}
if (a.position > b.position) {
return 1;
}
return 0;
}).map((contentItem) => {
if (contentItem.type === 'text') {
return {
'type': 'text',
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
'text': contentItem.txt,
}
}
} else if (contentItem.type === 'image') {
return {
'type': 'image',
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
'image_id': contentItem.img.id,
'image_url': contentItem.img.image,
}
}
} else if (contentItem.type === 'image-text') {
return {
'type': 'image-text',
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
'image_id': contentItem.img.id,
'image_url': contentItem.img.image,
'text': contentItem.txt,
}
}
} else if (contentItem.type === 'images') {
return {
'type': 'images',
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
'images': contentItem.gallery_images.map((galleryImage) => {
return {
'id': galleryImage.id ? galleryImage.id : null,
'img': galleryImage.img.id,
'src': galleryImage.img.image,
}
}),
}
}
} else if (contentItem.type === 'video') {
return {
'type': 'video',
'data': {
'id': contentItem.id ? contentItem.id : null,
'title': contentItem.title,
'video_url': contentItem.url,
}
}
}
});
},
addCourse: (courseJson, accessToken) => { addCourse: (courseJson, accessToken) => {
return axios.post('/api/v1/courses/', courseJson, { return axios.post('/api/v1/courses/', courseJson, {
headers: { headers: {
@ -211,4 +316,53 @@ export const api = {
} }
}); });
}, },
removeCourseLesson: (lessonId, accessToken) => {
return axios.delete(`/api/v1/lessons/${lessonId}/`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
addLesson: (lessonJson, accessToken) => {
return axios.post('/api/v1/lessons/', lessonJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
updateLesson: (lessonId, lessonJson, accessToken) => {
return axios.put(`/api/v1/lessons/${lessonId}/`, lessonJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
removeContentBlock: (blockData, accessToken) => {
let removeUrl;
switch(blockData.type) {
case 'text':
removeUrl = `/api/v1/texts/${blockData.data.id}/`;
break;
case 'image':
removeUrl = `/api/v1/images/${blockData.data.id}/`;
break;
case 'image-text':
removeUrl = `/api/v1/image-texts/${blockData.data.id}/`;
break;
case 'images':
removeUrl = `/api/v1/galleries/${blockData.data.id}/`;
break;
case 'video':
removeUrl = `/api/v1/videos/${blockData.data.id}/`;
break;
}
if (!removeUrl) {
return;
}
return axios.delete(removeUrl, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
}; };

@ -20,7 +20,6 @@ $(document).ready(function () {
} }
}) })
.done(function (data) { .done(function (data) {
console.log(data);
if (data.success === true) { if (data.success === true) {
if (replyToValue > 0) { if (replyToValue > 0) {
$(`#question__${replyToValue}`).after(data.comment); $(`#question__${replyToValue}`).after(data.comment);

@ -1,14 +1,14 @@
=d =d
@media only screen and (max-width: "1120px") @media only screen and (max-width: 1120px)
@content @content
=t =t
@media only screen and (max-width: "1023px") @media only screen and (max-width: 1023px)
@content @content
=m =m
@media only screen and (max-width: "599px") @media only screen and (max-width: 599px)
@content @content
=s =s
@media only screen and (max-width: "474px") @media only screen and (max-width: 474px)
@content @content
+font(ProximaNova-Regular, ProximaNova-Regular) +font(ProximaNova-Regular, ProximaNova-Regular)
@ -227,7 +227,7 @@ b
animation: loading .6s infinite linear animation: loading .6s infinite linear
@keyframes loading @keyframes loading
0 0%
transform: rotate(0deg) transform: rotate(0deg)
100% 100%
transform: rotate(360deg) transform: rotate(360deg)
@ -1945,6 +1945,10 @@ a.grey-link
background: none background: none
margin-top: 11px margin-top: 11px
.field__wrap .field__wrap
&--title
margin-right: 25px
&--additional
margin-top: 10px
svg.icon-password-eye svg.icon-password-eye
display: block display: block
svg.icon-password-hidden-eye svg.icon-password-hidden-eye
@ -3054,6 +3058,21 @@ a.grey-link
max-width: 620px max-width: 620px
&__section &__section
margin-bottom: 60px margin-bottom: 60px
&--block
box-shadow: 0 10px 50px 0 rgba(0, 0, 0, 0.06)
border-radius: 10px
padding: 15px
position: relative
&-remove
position: absolute
right: 15px
top: 20px
.icon-delete
width: 1.3em
height: 1.3em
transition: fill 0.3s ease
&:hover
fill: #000
&__nav &__nav
display: flex display: flex
margin: 0 -10px 60px margin: 0 -10px 60px

@ -1,5 +1,4 @@
// done by arturmoroz.com // done by arturmoroz.com
@import helpers/all @import helpers/all
@import generated/sprite-svg @import generated/sprite-svg
// @import lib/owl.carousel
@import common @import common

Loading…
Cancel
Save