LIL-348 - Получение черновика курса, LIL-243 - Автоматическое сохранение изменений курса, LIL-242 Уведомление о сохранении курса, LIl-239

remotes/origin/hasaccess
Vitaly Baev 8 years ago
parent 1d0a4695ec
commit 8d1c274975
  1. 4
      project/templates/lilcity/edit_index.html
  2. 5
      web/package-lock.json
  3. 1
      web/package.json
  4. 111
      web/src/components/CourseRedactor.vue
  5. 10
      web/src/js/course-redactor.js
  6. 7
      web/src/js/modules/api.js

@ -46,13 +46,13 @@
</button> </button>
<a class="header__logo logo" href="/"></a> <a class="header__logo logo" href="/"></a>
<div class="header__actions"> <div class="header__actions">
<div class="header__status">СОХРАНЕНО</div> <div id="course-redactor__saving-status" class="header__status"></div>
<button class="header__btn btn btn_icon"> <button class="header__btn btn btn_icon">
<svg class="icon icon-eye"> <svg class="icon icon-eye">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-eye"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-eye"></use>
</svg> </svg>
</button> </button>
<button class="header__btn btn">ОПУБЛИКОВАТЬ</button> <button id="course-redactor__publish-button" class="header__btn btn">ОПУБЛИКОВАТЬ</button>
</div> </div>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div class="header__login"> <div class="header__login">

@ -7041,6 +7041,11 @@
"lodash._bindcallback": "3.0.1" "lodash._bindcallback": "3.0.1"
} }
}, },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
"lodash.defaults": { "lodash.defaults": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",

@ -58,6 +58,7 @@
"history": "^4.7.2", "history": "^4.7.2",
"ilyabirman-likely": "^2.3.0", "ilyabirman-likely": "^2.3.0",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"lodash.debounce": "^4.0.8",
"moment": "^2.20.1", "moment": "^2.20.1",
"owl.carousel": "^2.2.0", "owl.carousel": "^2.2.0",
"slugify": "^1.2.9", "slugify": "^1.2.9",

@ -1,5 +1,5 @@
<template> <template>
<div> <div id="lilcity__course-redactor" v-on:course_publish="onCoursePublish">
<div v-if="!courseLoading"> <div v-if="!courseLoading">
<form v-if="viewSection !== 'lessons-edit'" @submit.prevent="onSubmit"> <form v-if="viewSection !== 'lessons-edit'" @submit.prevent="onSubmit">
<div class="info"> <div class="info">
@ -21,12 +21,15 @@
</div> </div>
</div> </div>
<div class="info__title"> <div class="info__title">
<div class="info__field field field_info"> <div class="info__field field field_info"
v-bind:class="{ error: ($v.course.title.$dirty || showErrors) && $v.course.title.$invalid }">
<div class="field__label">НАЗВАНИЕ КУРСА</div> <div class="field__label">НАЗВАНИЕ КУРСА</div>
<div class="field__wrap"> <div class="field__wrap">
<textarea class="field__textarea field__textarea_lg" <textarea class="field__textarea field__textarea_lg"
rows="1"
title="Название курса" title="Название курса"
v-autosize="course.title" v-autosize="course.title"
@input="$v.course.title.$touch()"
v-model="course.title"></textarea> v-model="course.title"></textarea>
</div> </div>
</div> </div>
@ -39,16 +42,18 @@
placeholder="Выберите категорию"/> placeholder="Выберите категорию"/>
</div> </div>
</div> </div>
<div class="info__field field field_info"> <div class="info__field field field_info"
v-bind:class="{ error: ($v.course.duration.$dirty || showErrors) && $v.course.duration.$invalid }">
<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"
@input="$v.course.duration.$touch()">
</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.number="displayPrice" :disabled="!course.is_paid">
</div> </div>
</div> </div>
</div> </div>
@ -116,12 +121,14 @@
<div class="section__center center"> <div class="section__center center">
<div class="kit"> <div class="kit">
<div class="kit__section"> <div class="kit__section">
<div class="kit__field field"> <div class="kit__field field"
v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }">
<div class="field__wrap"> <div class="field__wrap">
<textarea type="text" <textarea class="field__input"
class="field__input" rows="1"
placeholder="Кратко о курсе" placeholder="Кратко о курсе"
v-autosize="course.short_description" v-autosize="course.short_description"
@input="$v.course.short_description.$touch()"
v-model="course.short_description"></textarea> v-model="course.short_description"></textarea>
</div> </div>
</div> </div>
@ -139,6 +146,9 @@
:disabled="!course.id"> :disabled="!course.id">
Уроки Уроки
</button> </button>
<div v-if="!course.id" class="kit__nav-message">
Сохраните изменения в курсе, чтобы редактировать уроки
</div>
</div> </div>
<div v-if="viewSection === 'course'" class="kit__body"> <div v-if="viewSection === 'course'" class="kit__body">
<div v-for="(block, index) in course.content"> <div v-for="(block, index) in course.content">
@ -243,6 +253,8 @@
import LessonRedactor from "./LessonRedactor"; import LessonRedactor from "./LessonRedactor";
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
import BlockAdd from "./blocks/BlockAdd"; import BlockAdd from "./blocks/BlockAdd";
import debounce from 'lodash.debounce';
import {required, minValue, numeric } from 'vuelidate/lib/validators'
export default { export default {
name: "course-redactor", name: "course-redactor",
@ -315,9 +327,31 @@
'value': '18:00', 'value': '18:00',
} }
], ],
showErrors: false,
savingTimeout: null,
categoryOptions: [] categoryOptions: []
} }
}, },
validations: {
course: {
title: {
required
},
short_description: {
required
},
duration: {
required,
numeric,
minValue: minValue(1)
},
category: {
required,
numeric,
minValue: minValue(1)
}
},
},
methods: { methods: {
onCoverImageSelected(event) { onCoverImageSelected(event) {
let file = event.target.files[0]; let file = event.target.files[0];
@ -425,29 +459,62 @@
goToLessons() { goToLessons() {
this.viewSection = 'lessons'; this.viewSection = 'lessons';
}, },
loadCourse(courseId) { loadCourseDraft() {
this.courseLoading = true; this.courseLoading = true;
api.loadCourse(courseId, this.accessToken) api.getCourseDraft(this.accessToken)
.then((response) => { .then((response) => {
this.courseLoading = false; this.courseLoading = false;
this.course = api.convertCourseJson(response.data); this.course = api.convertCourseJson(response.data);
this.lessons = response.data.lessons.map((lessonJson) => {
return api.convertLessonJson(lessonJson);
});
}) })
.catch((err) => { .catch((err) => {
this.courseLoading = false; this.courseLoading = false;
console.log('error course loading', err); console.log('error course loading', err);
}); });
}, },
loadLessons(courseId) { loadCourse() {
api.getCourseLessons(courseId, this.accessToken) this.courseLoading = true;
api.loadCourse(this.courseId, this.accessToken)
.then((response) => { .then((response) => {
this.lessons = response.data.results.map((lessonJson) => { this.courseLoading = false;
this.course = api.convertCourseJson(response.data);
this.lessons = response.data.lessons.map((lessonJson) => {
return api.convertLessonJson(lessonJson); return api.convertLessonJson(lessonJson);
}); });
}) })
.catch((err) => { .catch((err) => {
this.courseLoading = false;
console.log('error course loading', err); console.log('error course loading', err);
}); });
} },
loadLessons(courseId) {
},
onCoursePublish() {
console.log('publish course');
},
saveCourseDraft: debounce(function (newValue, oldValue) {
if (!oldValue.id) {
return;
}
this.courseSaving = true;
clearTimeout(this.savingTimeout);
document.getElementById('course-redactor__saving-status').innerText = 'СОХРАНЕНИЕ';
api.saveCourse(this.course, this.accessToken)
.then((response) => {
this.courseSaving = false;
document.getElementById('course-redactor__saving-status').innerText = 'СОХРАНЕНО';
this.savingTimeout = setTimeout(() => {
document.getElementById('course-redactor__saving-status').innerText = '';
}, 2000);
// this.course = api.convertCourseJson(response.data);
})
.catch((err) => {
this.courseSaving = false;
});
}, 2000)
}, },
mounted() { mounted() {
api.getCategories(this.accessToken) api.getCategories(this.accessToken)
@ -464,8 +531,9 @@
}); });
if (this.courseId) { if (this.courseId) {
this.loadCourse(this.courseId); this.loadCourse()
this.loadLessons(this.courseId); } else {
this.loadCourseDraft();
} }
}, },
computed: { computed: {
@ -500,7 +568,16 @@
courseFullUrl() { courseFullUrl() {
let suffix = this.course.url ? this.course.url : 'ваша_ссылка'; let suffix = this.course.url ? this.course.url : 'ваша_ссылка';
return `https://lil.city/course/${suffix}`; return `https://lil.city/course/${suffix}`;
} },
},
watch: {
'course': {
handler: function (newValue, oldValue) {
this.saveCourseDraft(newValue, oldValue);
},
deep: true,
},
}, },
components: { components: {
BlockAdd, BlockAdd,

@ -1,7 +1,9 @@
import Vue from 'vue' import Vue from 'vue'
import VueAutosize from 'vue-autosize' import VueAutosize from 'vue-autosize'
import Vuelidate from 'vuelidate'
import 'babel-polyfill' import 'babel-polyfill'
import CourseRedactor from '../components/CourseRedactor.vue' import CourseRedactor from '../components/CourseRedactor.vue'
import $ from 'jquery';
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
// Enable vue-devtools // Enable vue-devtools
@ -9,6 +11,7 @@ if (process.env.NODE_ENV === 'development') {
} }
Vue.use(VueAutosize); Vue.use(VueAutosize);
Vue.use(Vuelidate);
let app = new Vue({ let app = new Vue({
el: '#lilcity-vue-app', el: '#lilcity-vue-app',
@ -16,3 +19,10 @@ let app = new Vue({
'course-redactor': CourseRedactor, 'course-redactor': CourseRedactor,
} }
}); });
$(document).ready(function () {
$('#course-redactor__publish-button').on('click', function () {
let event = new Event('course_publish');
document.getElementById('lilcity__course-redactor').dispatchEvent(event);
});
});

@ -372,4 +372,11 @@ export const api = {
} }
}); });
}, },
getCourseDraft: (accessToken) => {
return axios.get('/api/v1/courses/draft/', {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
}
}; };
Loading…
Cancel
Save