LIL-194 LIL-192 LIL-193 - Компоненты блоков курсов и уроков, конструктор курса (WIP)

remotes/origin/hasaccess
Vitaly Baev 8 years ago
parent 33b6a7e0cc
commit 6f76624212
  1. 3
      project/templates/lilcity/index.html
  2. 9
      project/templates/lilcity/test.html
  3. 1
      project/urls.py
  4. 8
      web/build/css/app.css
  5. 2
      web/build/css/app.css.map
  6. 1318
      web/build/js/app.js
  7. 2
      web/build/js/app.js.map
  8. 43431
      web/build/js/courseRedactor.js
  9. 1480
      web/package-lock.json
  10. 14
      web/package.json
  11. 378
      web/src/components/CourseRedactor.vue
  12. 37
      web/src/components/blocks/BlockImage.vue
  13. 46
      web/src/components/blocks/BlockImageText.vue
  14. 61
      web/src/components/blocks/BlockImages.vue
  15. 36
      web/src/components/blocks/BlockText.vue
  16. 43
      web/src/components/blocks/BlockVideo.vue
  17. 71
      web/src/components/inputs/LinkInput.vue
  18. 39
      web/src/components/redactor/VueRedactor.vue
  19. 57
      web/src/components/redactor/lang/ru.js
  20. 4
      web/src/components/redactor/redactor-full.js
  21. 4
      web/src/components/redactor/redactor-overrides.css
  22. 1230
      web/src/components/redactor/redactor.css
  23. 11179
      web/src/components/redactor/redactor.js
  24. 1
      web/src/components/redactor/redactor.min.css
  25. 5
      web/src/components/redactor/redactor.min.js
  26. 15
      web/src/js/course-redactor.js
  27. 4
      web/src/sass/_common.sass
  28. 36
      web/webpack.config.js

@ -162,7 +162,7 @@
<div class="message message_error">Необходимо подтвердить электронную почту</div>
{% endif %}
</header>
<div class="container">
<div id="lilcity-vue-app" class="container">
{% block content %}{% endblock content %}
</div>
<footer class="footer">
@ -481,6 +481,7 @@
</div>
</div>
<script type="text/javascript" src={% static "js/app.js" %}></script>
{% block foot %}{% endblock foot %}
</body>
</html>

@ -0,0 +1,9 @@
{% extends "templates/lilcity/index.html" %} {% load static %}
{% block title %}School LIL.CITY{% endblock title %}
{% block content %}
<course-redactor author-picture="http://localhost:8000/static/img/user.jpg" author-name="Vitaly Baev"></course-redactor>
{% endblock content %}
{% block foot %}
<script type="text/javascript" src={% static "js/courseRedactor.js" %}></script>
{% endblock foot %}

@ -42,6 +42,7 @@ urlpatterns = [
path('refund-policy', TemplateView.as_view(template_name="templates/lilcity/refund_policy.html"), name='refund_policy'),
path('', TemplateView.as_view(template_name="templates/lilcity/main.html", extra_context={'course_items': Course.objects.all()[:3]}), name='index'),
path('api/v1/', include(('api.v1.urls', 'api_v1'))),
path('test', TemplateView.as_view(template_name="templates/lilcity/test.html"), name='test'),
]

@ -3744,6 +3744,8 @@ a.grey-link:hover {
padding: 30px 40px 50px;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-positive: 1;
flex-grow: 1;
}
.info__head {
@ -3824,13 +3826,11 @@ a.grey-link:hover {
margin-bottom: 50px;
}
.info__fieldset:last-child {
margin-top: auto;
}
.add {
display: -ms-flexbox;
display: flex;
-ms-flex-align: start;
align-items: flex-start;
}
.add__toggle {

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1480
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -15,8 +15,11 @@
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.3.13",
"browser-sync": "^2.10.0",
"css-loader": "^0.28.9",
"css-mqpacker": "^5.0.1",
"del": "^2.2.0",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-changed": "^1.3.0",
@ -40,15 +43,24 @@
"lodash": "^4.3.0",
"require-dir": "^0.3.0",
"run-sequence": "^1.1.5",
"sass-loader": "^6.0.6",
"style-loader": "^0.20.1",
"through2": "^2.0.1",
"url-loader": "^0.6.2",
"vue-loader": "^14.1.1",
"vue-style-loader": "^3.1.2",
"vue-template-compiler": "^2.5.13",
"webpack": "^3.10.0"
},
"dependencies": {
"babel-polyfill": "^6.26.0",
"history": "^4.7.2",
"jquery": "^3.3.1",
"moment": "^2.20.1",
"owl.carousel": "^2.2.0",
"smooth-scroll": "^12.1.5",
"validator": "^9.2.0"
"validator": "^9.2.0",
"vue": "^2.5.13",
"vuejs-datepicker": "^0.9.25"
}
}

@ -0,0 +1,378 @@
<template>
<div>
<div class="info">
<div class="info__section" :style="backgroundStyle">
<div class="info__main">
<div class="info__head">
<div class="info__user">
<div class="info__ava ava">
<img :src="authorPicture" alt="Аватар" class="ava__pic">
</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="onBackgroundImageSelected">
</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="Название курса"
v-model="course.title"></textarea>
</div>
</div>
</div>
<div class="info__foot">
<div class="info__field field field_info">
<div class="field__label">КАТЕГОРИЯ</div>
<div class="field__wrap">
<input type="text" class="field__input" v-model="course.category">
</div>
</div>
<div class="info__field field field_info">
<div class="field__label">ПРОДОЛЖИТЕЛЬНОСТЬ</div>
<div class="field__wrap">
<input type="text" class="field__input" v-model.number="course.duration">
</div>
</div>
<div class="info__field field field_info">
<div class="field__label">СТОИМОСТЬ</div>
<div class="field__wrap">
<input type="text" class="field__input" v-model="displayPrice" :disabled="!course.is_paid">
</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>
<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 class="info__fieldset">
<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="false" class="switch__input" v-model="course.is_delayed">
<span class="switch__content">Мгновенный</span>
</label>
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="true" class="switch__input" v-model="course.is_delayed">
<span class="switch__content">Отложенный</span>
</label>
</div>
</div>
<div class="info__field field">
<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>
</div>
</div>
</div>
<div class="section">
<div class="section__center center">
<div class="kit">
<div class="kit__section">
<div class="kit__field field">
<div class="field__wrap">
<input type="text" class="field__input" placeholder="Кратко о курсе" v-model="course.short_description">
</div>
</div>
</div>
<div class="kit__nav">
<button class="kit__btn btn btn_lg btn_stroke">Описание курса</button>
<button class="kit__btn btn btn_lg btn_gray">Уроки</button>
</div>
<div class="kit__body">
<div v-for="block in course.blocks">
<block-text v-if="block.type === 'text'" :title.sync="block.data.title" :text.sync="block.data.text"/>
<block-image-text v-if="block.type === 'image-text'" :title.sync="block.data.title"
:text.sync="block.data.text"/>
<block-image v-if="block.type === 'image'" :title.sync="block.data.title"/>
<block-images v-if="block.type === 'images'" :title.sync="block.data.title" :text.sync="block.data.text"
:images.sync="block.data.images"/>
<block-video v-if="block.type === 'video'" :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 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 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 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 class="add__btn" @click="addBlockImages">
<svg class="icon icon-images">
<use xlink:href="/static/img/sprite.svg#icon-images"></use>
</svg>
</button>
<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>
<div class="kit__section">
<div class="kit__theme">О чем курс?</div>
<vue-redactor :value.sync="course.about" />
</div>
<div class="kit__foot">
<button class="kit__submit btn btn_md">Сохранить</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import LinkInput from './inputs/LinkInput'
import Datepicker from 'vuejs-datepicker'
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 VueRedactor from './redactor/VueRedactor'
export default {
name: "course-redactor",
props: ["authorName", "authorPicture"],
data() {
return {
course: {
title: 'Как просто научиться рисовать простых персонажей.',
category: 'Акварель',
duration: 1,
price: 1000,
url: 'my-awesome-url',
backgroundImage: '',
is_paid: true,
is_featured: true,
is_delayed: false,
date: '2018-03-08',
short_description: 'Этот курс поможет детям освоить базовые навыки рисования простых персонажей',
blocks: [],
about: '<b>Главная цель курса</b> - рассказать...',
},
is_adding_block: false
}
},
methods: {
onBackgroundImageSelected(event) {
let file = event.target.files[0];
let reader = new FileReader();
reader.onload = () => {
this.course.backgroundImage = reader.result;
};
if (file) {
reader.readAsDataURL(file);
}
},
onCoursePriceChange(event) {
console.log('onCoursePriceChange');
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: 'тест картинка',
}
})
},
addBlockImageText() {
this.course.blocks.push({
type: 'image-text',
data: {
title: 'тест картинка-текст',
text: 'текст какой-то',
}
})
},
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',
}
})
}
},
computed: {
backgroundStyle() {
return this.course.backgroundImage ? `background-image: url(${this.course.backgroundImage});` : '';
},
displayPrice: {
get: function () {
return this.course.is_paid ? this.course.price : 0;
},
set: function (value) {
this.course.price = value;
}
}
},
components: {
BlockText,
'link-input': LinkInput,
'vue-datepicker': Datepicker,
'block-text': BlockText,
'block-image': BlockImage,
'block-image-text': BlockImageText,
'block-images': BlockImages,
'block-video': BlockVideo,
'vue-redactor': VueRedactor,
}
}
</script>
<style lang="scss">
.vdp-datepicker__calendar {
width: 240px;
margin-top: 10px;
padding: 5px;
background: white;
box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.1);
z-index: 99 !important;
header {
display: flex;
margin-bottom: 5px;
-ms-flex-align: center;
align-items: center;
}
.prev, .next {
font-size: 0;
cursor: pointer;
order: 1;
width: auto !important;
padding: 10px;
}
.prev {
order: 1;
}
.next {
order: 3;
}
.prev:before, .next:before {
content: '';
display: block;
width: 10px;
height: 10px;
border: solid #E6E6E6;
border-width: 2px 2px 0 0;
}
.prev:after, .next:after {
content: none !important;
}
.prev:before {
transform: rotate(-135deg);
}
.next:before {
transform: rotate(45deg);
}
}
.kit__preview {
img {
width: 140px;
height: 140px;
}
}
.kit__photo {
width: 140px;
height: 140px;
}
</style>

@ -0,0 +1,37 @@
<template>
<div class="kit__section">
<div class="kit__field field">
<div class="field__wrap">
<input type="text"
:value="title"
class="field__input"
placeholder="Заголовок раздела"
@change="onTitleChange">
</div>
</div>
<div class="kit__row">
<div class="kit__photo">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
<input type="file" class="kit__file">
</div>
</div>
</div>
</template>
<script>
export default {
name: "block-image",
props: ["title"],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);
},
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,46 @@
<template>
<div class="kit__section">
<div class="kit__row">
<div class="kit__photo">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
<input type="file" class="kit__file">
</div>
<div class="kit__fieldset">
<div class="kit__field field">
<div class="field__wrap">
<input type="text"
:value="title"
class="field__input"
placeholder="Заголовок раздела"
@change="onTitleChange">
</div>
</div>
<div class="kit__field field">
<div class="field__wrap">
<textarea class="field__textarea field__textarea_sm"
placeholder="Описание"
:value="text"
@change="onTextChange"></textarea>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "block-image-text",
props: ["title", "text", 'image'],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);
},
onTextChange(event) {
this.$emit('update:text', event.target.value);
}
}
}
</script>

@ -0,0 +1,61 @@
<template>
<div class="kit__section">
<div class="kit__field field">
<div class="field__wrap">
<input type="text"
:value="title"
class="field__input"
placeholder="Заголовок раздела"
@change="onTitleChange">
</div>
</div>
<div class="kit__field field">
<div class="field__wrap">
<textarea class="field__textarea field__textarea_sm"
:value="text"
placeholder="Описание"
@change="onTextChange"></textarea>
</div>
</div>
<div class="kit__gallery">
<div class="kit__preview" v-for="image in images">
<img :src="image.src" class="kit__pic">
</div>
<div class="kit__photo">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
<input type="file" class="kit__file" @change="onImageAdded">
</div>
</div>
</div>
</template>
<script>
export default {
name: "block-images",
props: ["title", "text", "images"],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);
},
onTextChange(event) {
this.$emit('update:text', event.target.value);
},
onImageAdded(event) {
let file = event.target.files[0];
let reader = new FileReader();
reader.onload = () => {
let images = this.images;
images.push({
src: reader.result,
});
this.$emit('update:images', images);
};
if (file) {
reader.readAsDataURL(file);
}
}
}
}
</script>

@ -0,0 +1,36 @@
<template>
<div class="kit__section">
<div class="kit__field field">
<div class="field__wrap">
<input type="text"
:value="title"
class="field__input"
placeholder="Заголовок раздела"
@change="onTitleChange">
</div>
</div>
<div class="kit__field field">
<div class="field__wrap">
<textarea class="field__textarea field__textarea_sm"
:value="text"
placeholder="Описание"
@change="onTextChange"></textarea>
</div>
</div>
</div>
</template>
<script>
export default {
name: "block-text",
props: ["title", "text"],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);
},
onTextChange(event) {
this.$emit('update:text', event.target.value);
}
}
}
</script>

@ -0,0 +1,43 @@
<template>
<div class="kit__section">
<div class="kit__field field">
<div class="field__wrap">
<input type="text"
:value="title"
class="field__input"
placeholder="Заголовок раздела"
@change="onTitleChange">
</div>
</div>
<div class="kit__field field">
<div class="field__wrap">
<div class="field__flex">
<input type="text"
:value="videoUrl"
class="field__input field__input_sm"
placeholder="Вставьте ссылку на Vimeo, YouTube, или другой сервис"
@change="onVideoUrlChange">
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "block-video",
props: ["title", "videoUrl"],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);
},
onVideoUrlChange(event) {
this.$emit('update:videoUrl', event.target.value);
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,71 @@
<template>
<div class="link-input__container">
<input type="text"
class="field__input"
:style="prefixPadding"
v-model="value"
ref="input">
<div class="link-input__prefix-wrapper" @click="onPrefixClick">
<div class="link-input__prefix" ref="prefix">{{ this.prefix }}</div>
</div>
</div>
</template>
<script>
export default {
name: "link-input",
props: ["prefix", "url"],
data() {
return {
prefixWidth: 0,
value: this.url,
}
},
mounted() {
this.updatePrefixWidth();
},
methods: {
updatePrefixWidth() {
this.$nextTick(() => {
this.prefixWidth = this.$refs.prefix.clientWidth;
});
},
onPrefixClick() {
this.$refs.input.focus();
},
},
computed: {
prefixPadding() {
return `padding-left: ${this.prefixWidth + 2}px`;
}
},
watch: {
prefix() {
this.updatePrefixWidth();
},
value(newValue) {
this.$emit('update:url', newValue)
}
}
}
</script>
<style scoped>
.link-input__container {
position: relative;
}
.link-input__prefix-wrapper {
position: absolute;
left: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
}
.link-input__prefix {
font-size: 18px;
margin-bottom: 1px;
}
</style>

@ -0,0 +1,39 @@
<template>
<textarea ref="input">{{ content }}</textarea>
</template>
<script>
import $ from 'jquery';
import './redactor-full.js';
export default {
props: ['value'],
name: "vue-redactor",
data() {
return {
content: this.value
}
},
mounted() {
const me = this;
$(me.$refs.input).redactor({
air: true,
formatting: ['p', 'blockquote', 'h2', 'h3', 'h4'],
lang: 'ru',
placeholder: 'Напишите, о чем ваш курс',
callbacks: {
visual: function () {
me.$emit('update:value', this.code.get());
},
sync: function () {
me.$emit('update:value', this.code.get());
}
},
});
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,57 @@
(function ($)
{
$.Redactor.opts.langs['ru'] = {
"format": "Формат",
"image": "Картинка",
"file": "Файл",
"link": "Ссылка",
"bold": "Полужирный",
"italic": "Курсив",
"deleted": "Зачеркнутый",
"underline": "Подчеркнутый",
"bold-abbr": "B",
"italic-abbr": "I",
"deleted-abbr": "S",
"underline-abbr": "U",
"lists": "Списки",
"link-insert": "Вставить ссылку",
"link-edit": "Редактировать ссылку",
"link-in-new-tab": "Открыть ссылку в новом табе",
"unlink": "Удалить ссылку",
"cancel": "Отменить",
"close": "Закрыть",
"insert": "Вставить",
"save": "Сохранить",
"delete": "Удалить",
"text": "Текст",
"edit": "Редактировать",
"title": "Title",
"paragraph": "Обычный текст",
"quote": "Цитата",
"code": "Код",
"heading1": "Заголовок 1",
"heading2": "Заголовок 2",
"heading3": "Заголовок 3",
"heading4": "Заголовок 4",
"heading5": "Заголовок 5",
"heading6": "Заголовок 6",
"filename": "Имя файла",
"optional": "необязательно",
"unorderedlist": "Ненумерованный список",
"orderedlist": "Нумерованный список",
"outdent": "Убрать отступ",
"indent": "Добавить отступ",
"horizontalrule": "Линия",
"upload-label": "Перетащите файл или ",
"accessibility-help-label": "Редактор форматированного текста",
"caption": "Подпись",
"bulletslist": "Маркеры",
"numberslist": "Нумерация",
"image-position": "Position",
"none": "None",
"left": "Left",
"right": "Right",
"center": "Center"
};
})(jQuery);

@ -0,0 +1,4 @@
import './redactor.js'
import './lang/ru'
import './redactor.css'
import './redactor-overrides.css'

@ -0,0 +1,4 @@
.redactor-layer {
border: none;
padding: 0;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,15 @@
import Vue from 'vue'
import 'babel-polyfill'
import CourseRedactor from '../components/CourseRedactor.vue'
if (process.env.NODE_ENV === 'development') {
// Enable vue-devtools
Vue.config.devtools = true;
}
let app = new Vue({
el: '#lilcity-vue-app',
components: {
'course-redactor': CourseRedactor,
}
});

@ -2942,6 +2942,7 @@ a.grey-link
height: 550px
padding: 30px 40px 50px
flex-direction: column
flex-grow: 1
+t
width: 100%
max-width: 100%
@ -2996,11 +2997,10 @@ a.grey-link
&__fieldset
&:first-child
margin-bottom: 50px
&:last-child
margin-top: auto
.add
display: flex
align-items: flex-start
&__toggle
font-size: 0
&__circle,

@ -4,7 +4,8 @@ const NODE_ENV = process.env.NODE_ENV || 'development';
module.exports = {
entry: {
app: "./src/js/app.js"
app: "./src/js/app.js",
courseRedactor: "./src/js/course-redactor.js"
},
output: {
path: path.join(__dirname, "build/js"),
@ -28,6 +29,26 @@ module.exports = {
]
}
}
},
{
test: /\.css$/,
loader: 'style-loader!css-loader'
},
{
test: /\.vue$/,
loader: 'vue-loader',
exclude: [/node_modules/],
options: {
loaders: {
css: 'vue-style-loader!css-loader',
scss: 'vue-style-loader!css-loader!sass-loader',
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
}
}
},
{
test: /\.(png|gif|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000'
}
]
},
@ -36,9 +57,20 @@ module.exports = {
'process.env': {
'NODE_ENV': JSON.stringify(NODE_ENV)
}
})
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery"
}),
],
resolve: {
alias: {
vue: 'vue/dist/vue.js'
},
extensions: ['*', '.js', '.vue']
},
watch: NODE_ENV === 'development',

Loading…
Cancel
Save