I'm using ckeditor5 balloon block mode in nuxt project.
I have used online builder and downloaded build files , add the build files to my project and importing them into my editor component and using it!
the only problem that I have is that when the page loads ,
I get an error : editor-isreadonly-has-no-setter.
I tried binding v-model to the editor but the value won't be updated!
note : I have used ckeditor5 classic mode identical to the way that I'm using Balloon Block, donno really what's going on!
this is my component :
<template>
<ckeditor
:id="id"
v-bind="$attrs"
:editor="BalloonBlock"
:config="editorConfig"
v-on="$listeners"
/>
</template>
<script>
let BalloonBlock
let CKEditor
if (process.client) {
BalloonBlock = require('#/plugins/ckeditor/ckeditor')
CKEditor = require('#ckeditor/ckeditor5-vue2')
} else {
CKEditor = { component: { template: '<div></div>' } }
}
export default {
name: 'CKEditor',
components: {
ckeditor: CKEditor.component,
},
props: {
fillErr: {
type: Boolean,
default: false,
required: false,
},
minHeight: {
type: String,
default: '350px',
required: false,
},
label: {
type: String,
default: '',
required: false,
},
},
data() {
return {
classicEditor: BalloonBlock,
editorConfig: {
language: 'fa',
contentsLangDirection: 'rtl',
},
editorElement: null,
id: null,
}
},
computed: {
value() {
return this.$attrs.value
},
},
created() {
this.id = this.uuidv4()
},
mounted() {
if (!document.getElementById('editorFaTranslate')) {
const faScript = document.createElement('script')
faScript.setAttribute('charset', 'utf-8')
faScript.setAttribute('type', 'text/js')
faScript.setAttribute('id', 'editorFaTranslate')
faScript.setAttribute(
'src',
require('##/plugins/ckeditor/translations/fa.js')
)
document.head.appendChild(faScript)
}
const intervalId = setInterval(() => {
const ckEditor = document.getElementById(this.id)
if (ckEditor) {
clearInterval(intervalId)
this.editorElement = ckEditor
}
})
},
methods: {
uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
function (c) {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
}
)
},
insertTextAtTheEnd(text) {
function findCorrectPosition(htmlStr) {
const lastIndexOfHTMLTag = htmlStr.lastIndexOf('</')
const lastUlTag = htmlStr.lastIndexOf('</ul>')
const lastOlTag = htmlStr.lastIndexOf('</ol>')
if (
lastUlTag === lastIndexOfHTMLTag ||
lastOlTag === lastIndexOfHTMLTag
) {
const lastLiTag = htmlStr.lastIndexOf('</li>')
return lastLiTag
}
return lastIndexOfHTMLTag
}
const currentString = this.value
const correctIndex = findCorrectPosition(currentString)
const firstHalf = currentString.substring(0, correctIndex)
const secondHalf = currentString.substring(correctIndex)
const newString = `${firstHalf}${text}${secondHalf}`
this.$emit('input', newString)
},
},
}
</script>
I would welcome any idea!
I added "#ckeditor/ckeditor5-vue2": "github:ckeditor/ckeditor5-vue2", in my dependencies and all of a sudden my problem was gone!
I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.
Problem
Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.
video of the problem
As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.
Code
import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "#/services/api/resources/tags/codec";
import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from "quasar";
export const TagAutoComplete = defineComponent({
name: "TagAutoComplete",
props: {
modelValue: { type: Array as PropType<TagCodec[]> },
},
emits: ["update:modelValue"],
setup(props, context) {
const loading = ref(false);
const tags = ref<TagCodec[]>([]);
// eslint-disable-next-line #typescript-eslint/ban-types
const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
const parameters = val === "" ? {} : { title: val };
doneFn(async () => {
loading.value = true;
const response = await list(parameters);
if (val) {
const needle = val.toLowerCase();
tags.value = response.data.data.filter(
(tag) => tag.title.toLowerCase().indexOf(needle) > -1
);
} else {
tags.value = response.data.data;
}
loading.value = false;
});
};
const onInput = (values: TagCodec[]) => {
context.emit("update:modelValue", values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: "title",
optionValue: "id",
outlined: true,
useInput: true,
useChips: true,
placeholder: "Start typing to search",
onFilter: onFilterTest,
"onUpdate:modelValue": onInput,
loading: loading.value,
});
};
},
});
What I have tried
I have tried to use the several props that is available for the component but nothing seemed to work.
My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.
Questions
Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?
It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.
const onFilterTest = async (val, update /* abort */) => {
const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await list(parameters);
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
I tested it by the following code and mocked values.
// import type { PropType } from 'vue';
import { defineComponent, h, ref } from 'vue';
// import type { TagCodec } from "#/services/api/resources/tags/codec";
// import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from 'quasar';
export const TagAutoComplete = defineComponent({
name: 'TagAutoComplete',
props: {
modelValue: { type: [] },
},
emits: ['update:modelValue'],
setup(props, context) {
const loading = ref(false);
const tags = ref([]);
const onFilterTest = async (val, update /* abort */) => {
// const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
data: [
{
id: 1,
title: 'Vue',
},
{
id: 2,
title: 'Vuex',
},
{
id: 3,
title: 'Nuxt',
},
{
id: 4,
title: 'SSR',
},
],
},
});
}, 3000);
});
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
const onInput = (values) => {
context.emit('update:modelValue', values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: 'title',
optionValue: 'id',
outlined: true,
useInput: true,
useChips: true,
placeholder: 'Start typing to search',
onFilter: onFilterTest,
'onUpdate:modelValue': onInput,
loading: loading.value,
});
};
},
});
i am using react-quill as my editor and recently i configured my image handler function to pass props to the handler and after making the change my editor behaves weirdly and when ever i type something on the other input fields my editor comes into focus and automatically words are typed in it
below is the code for my editor
please any help or suggestion will be greatly appreciated.
Component
} from 'react';
// import {
// Editor
// } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
import axios from 'axios'
import {
API_URL
} from './../../api_url'
// import * as Icons from 'images/icons';
import * as loadImage from 'blueimp-load-image';
import {
key
} from '../../assets/encryptionkey'
import globalStyles from '../../stylesheets/ui.css'
import blogStyles from './blogs.css'
import bootstrapStyles from '../../stylesheets/bootstrap/css/bootstrap.min.css'
import fontAwesomeStyles from '../../stylesheets/font-awesome/css/font-awesome.min.css'
import actionIconsStyles from '../../stylesheets/action_icons.css'
import cx from 'classnames'
import './editor.css';
import s from './editor.css';
//import CKEditor from '#ckeditor/ckeditor5-react';
//import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
//import ReactQuill, { Quill } from "react-quill";
//var Image = Quill.import('formats/image');
//Image.className = 'custom-class-to-image';
//Quill.register(Image, true);
export default class BlogEditor extends Component {
constructor( loader ) {
super();
this.state = {
editorHtml: '', theme: 'snow',
text:''
}
this.handleChange = this.handleChange.bind(this)
// var that=this;
if (typeof window !== 'undefined') {
this.ReactQuill = require('react-quill')
const ReactQuill=this.ReactQuill;
var Image = ReactQuill.Quill.import('formats/image');
Image.className = 'blog-content-image';
ReactQuill.Quill.register(Image, true);
// ReactQuill.Quill.setContents(editor.clipboard.convert(html));
}
}
componentWillReceiveProps(){
//debugger
let clearContent=this.props.clearContent
if(clearContent){
// this.editorRef.setEditorContents(this.editorRef.getEditor(), '<h1>test</h1>');
}
}
handleChange(value) {
//debugger
this.setState({ text: value })
// this.props.changeInEditor(value)
}
imageHandler({ val, componentProps }) {
// debugger
let self=this
let image;
let image_extension;
const Cryptr = require('cryptr');
const cryptr = new Cryptr(key);
const users = localStorage.getItem('users') ? JSON.parse(cryptr.decrypt(localStorage.getItem('users'))) : {}
// console.log(users[users.lastLoginId])
let loggedinUser = users[users.lastLoginId];
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.setAttribute("class", "Editor-mage");
input.click();
input.onchange = async () => {
//debugger
const file = input.files[0];
var ValidImageTypes = ["image/gif", "image/jpeg", "image/png", "image/jpg", "image/GIF", "image/JPEG", "image/PNG", "image/JPG"];
let file_type = file.type
let filename = file.name
let extension = filename.split('.').pop();
if(ValidImageTypes.indexOf(file_type) >= 0){
if(file.size<=500000&&file.size>=50000){
var fileToLoad = file
loadImage(fileToLoad, (canvas) => {
if(canvas){
// this.setState({
image=canvas.toDataURL()
image_extension=extension
//});
const res = new Promise(function(resolve, reject) {
axios({
method:'post',
url:API_URL+'api/v1/postblogimage',
headers:{
'x-access-handler':loggedinUser.token
},
data:{
image: image,
image_extension:image_extension,
userid:loggedinUser.userid
}
})
//axios.post(API_URL + 'api/v1/postblogimage', formData, config)
.then((response) => {
if (response.data.error == 'false' || response.data.error == false) {
if (response.data.status == 200 && response.data.message == "Image uploaded successfully") {
//debugger
const range = self.quill.getSelection(true);
// Insert temporary loading placeholder image
// this.quill.insertEmbed(range.index, 'image', `${window.location.origin}/images/loaders/placeholder.gif`);
// Move cursor to right side of image (easier to continue typing)
self.quill.setSelection(range.index + 1);
// Remove placeholder image
self.quill.deleteText(range.index, 1);
// Insert uploaded image
let url=response.data.data[0].imageURL;
self.quill.insertEmbed(range.index, 'image', url);
self.quill.pasteHTML(range.index, <img src={url} class="blog-image-content" alt="Responsive image"/>);
}
}else if(response.data.error == 'true' || response.data.status == '500')
componentProps.error('Sorry, Inappropriate image')
// }
}).catch((error) => {
// reject(Error("It broke"));
});
});
}
}, {orientation: true});
}
else{
componentProps.error(" Sorry, File size should be of size between 50 kb to 500kb")
}
}
else{
// this.setState({
// image_warning:'Invalid image type',
// image:'',
// image_extension:''
//})
// this.fileInput.value=''
}
};
}
render() {
const ReactQuill = this.ReactQuill
if (typeof window !== 'undefined' && ReactQuill) {
return (
<div className="editor-container">
<ReactQuill
ref={(el) => this.quillRef = el
}
onChange={this.handleChange}
placeholder={"share your thoughts"}
modules={{
toolbar: {
container: [
[{ header: '1' }, { header: [2,3, 4, 5, 6] }, { font: [] }],
[{ size: [ 'small', false, 'large', 'huge' ] }],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image', 'video'],
['clean'],
['code-block'],
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
[{ 'align': [] }],
],
handlers: {
image: (val) => this.imageHandler({ val, componentProps: this.props })
// image: () => this.imageHandler
}
}
}}
/>
</div>
)
}
else {
return <textarea />;
}
}
}```
Each re-render modules object creating, useMemo fixed.
const modules = useMemo(() => ({
imageResize : {
parchment: Quill.import('parchment'),
modules: ['Resize', 'DisplaySize', 'Toolbar'],
},
toolbar: {
container: [
[{ header: [1, 2, 3, 4, false] }],
["bold", "italic", "underline", "strike", "blockquote"],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
],
[{align: [ ]}],
["link", "image"],
["clean"],
],
handlers: {
image: () => {
imageHandler()
}
}
},
}), []);
Below is the original gulpfile that came when running the generator-angular-fullstack (basically - I removed a task from running in development).
I have HTML files in 'client/components/modal/templates/' that are not getting added to the project when I run gulp build, so when I try serving the built code, I got an error saying:
"Failed to load template: ./components/modal/templates/event.html (HTTP status: 404 Not Found)"
Can someone tell me what to change so the files in this location are brought into the project?
// Generated on 2016-06-21 using generator-angular-fullstack 3.7.6
'use strict';
import _ from 'lodash';
import del from 'del';
import gulp from 'gulp';
import grunt from 'grunt';
import path from 'path';
import gulpLoadPlugins from 'gulp-load-plugins';
import http from 'http';
import open from 'open';
import lazypipe from 'lazypipe';
import {stream as wiredep} from 'wiredep';
import nodemon from 'nodemon';
import {Server as KarmaServer} from 'karma';
import runSequence from 'run-sequence';
import {protractor, webdriver_update} from 'gulp-protractor';
import {Instrumenter} from 'isparta';
var plugins = gulpLoadPlugins();
var config;
const clientPath = require('./bower.json').appPath || 'client';
const serverPath = 'server';
const paths = {
client: {
assets: `${clientPath}/assets/**/*`,
images: `${clientPath}/assets/images/**/*`,
scripts: [
`${clientPath}/**/!(*.spec|*.mock).ts`,
`!${clientPath}/bower_components/**/*`,
`!${clientPath}/{typings,test_typings}/**/*`
],
styles: [`${clientPath}/{app,components}/**/*.less`],
mainStyle: `${clientPath}/app/app.less`,
views: `${clientPath}/{app,components}/**/*.html`,
mainView: `${clientPath}/index.html`,
test: [`${clientPath}/{app,components}/**/*.{spec,mock}.ts`],
e2e: ['e2e/**/*.spec.js'],
bower: `${clientPath}/bower_components/`
},
server: {
scripts: [
`${serverPath}/**/!(*.spec|*.integration).js`,
`!${serverPath}/config/local.env.sample.js`
],
json: [`${serverPath}/**/*.json`],
test: {
integration: [`${serverPath}/**/*.integration.js`, 'mocha.global.js'],
unit: [`${serverPath}/**/*.spec.js`, 'mocha.global.js']
}
},
karma: 'karma.conf.js',
dist: 'dist'
};
/********************
* Helper functions
********************/
function onServerLog(log) {
console.log(plugins.util.colors.white('[') +
plugins.util.colors.yellow('nodemon') +
plugins.util.colors.white('] ') +
log.message);
}
function checkAppReady(cb) {
var options = {
host: 'localhost',
port: config.port
};
http
.get(options, () => cb(true))
.on('error', () => cb(false));
}
// Call page until first success
function whenServerReady(cb) {
var serverReady = false;
var appReadyInterval = setInterval(() =>
checkAppReady((ready) => {
if (!ready || serverReady) {
return;
}
clearInterval(appReadyInterval);
serverReady = true;
cb();
}),
100);
}
function sortModulesFirst(a, b) {
var module = /\.module\.ts$/;
var aMod = module.test(a.path);
var bMod = module.test(b.path);
// inject *.module.js first
if (aMod === bMod) {
// either both modules or both non-modules, so just sort normally
if (a.path < b.path) {
return -1;
}
if (a.path > b.path) {
return 1;
}
return 0;
} else {
return (aMod ? -1 : 1);
}
}
/********************
* Reusable pipelines
********************/
let lintClientScripts = lazypipe()
.pipe(plugins.tslint, require(`./${clientPath}/tslint.json`))
.pipe(plugins.tslint.report, 'verbose');
let lintServerScripts = lazypipe()
.pipe(plugins.jshint, `${serverPath}/.jshintrc`)
.pipe(plugins.jshint.reporter, 'jshint-stylish');
let lintServerTestScripts = lazypipe()
.pipe(plugins.jshint, `${serverPath}/.jshintrc-spec`)
.pipe(plugins.jshint.reporter, 'jshint-stylish');
let styles = lazypipe()
.pipe(plugins.sourcemaps.init)
.pipe(plugins.less)
.pipe(plugins.autoprefixer, {browsers: ['last 1 version']})
.pipe(plugins.sourcemaps.write, '.');
let transpileServer = lazypipe()
.pipe(plugins.sourcemaps.init)
.pipe(plugins.babel, {
plugins: [
'transform-class-properties',
'transform-runtime'
]
})
.pipe(plugins.sourcemaps.write, '.');
let mocha = lazypipe()
.pipe(plugins.mocha, {
reporter: 'spec',
timeout: 5000,
require: [
'./mocha.conf'
]
});
let istanbul = lazypipe()
.pipe(plugins.istanbul.writeReports)
.pipe(plugins.istanbulEnforcer, {
thresholds: {
global: {
lines: 80,
statements: 80,
branches: 80,
functions: 80
}
},
coverageDirectory: './coverage',
rootDirectory : ''
});
/********************
* Env
********************/
gulp.task('env:all', () => {
let localConfig;
try {
localConfig = require(`./${serverPath}/config/local.env`);
} catch (e) {
localConfig = {};
}
plugins.env({
vars: localConfig
});
});
gulp.task('env:test', () => {
plugins.env({
vars: {NODE_ENV: 'test'}
});
});
gulp.task('env:prod', () => {
plugins.env({
vars: {NODE_ENV: 'production'}
});
});
/********************
* Tasks
********************/
gulp.task('inject', cb => {
runSequence(['inject:js', 'inject:css', 'inject:less', 'inject:tsconfig'], cb);
});
gulp.task('inject:js', () => {
return gulp.src(paths.client.mainView)
.pipe(plugins.inject(
gulp.src(_.union(paths.client.scripts, ['client/app/app.constant.js', `!${clientPath}/**/*.{spec,mock}.ts`, `!${clientPath}/app/app.ts`]), {read: false})
.pipe(plugins.sort(sortModulesFirst)),
{
starttag: '<!-- injector:js -->',
endtag: '<!-- endinjector -->',
transform: (filepath) => '<script src="' + filepath.replace(`/${clientPath}/`, '').replace('.ts', '.js') + '"></script>'
}))
.pipe(gulp.dest(clientPath));
});
function injectTsConfig(filesGlob, tsconfigPath){
let src = gulp.src(filesGlob, {read: false})
.pipe(plugins.sort());
return gulp.src(tsconfigPath)
.pipe(plugins.inject(src, {
starttag: '"files": [',
endtag: ']',
transform: (filepath, file, i, length) => {
return `"${filepath.substr(1)}"${i + 1 < length ? ',' : ''}`;
}
}))
.pipe(gulp.dest('./'));
}
gulp.task('inject:tsconfig', () => {
return injectTsConfig([
`${clientPath}/**/!(*.spec|*.mock).ts`,
`!${clientPath}/bower_components/**/*`,
`typings/main.d.ts`
],
'./tsconfig.client.json');
});
gulp.task('inject:tsconfigTest', () => {
return injectTsConfig([
`${clientPath}/**/+(*.spec|*.mock).ts`,
`!${clientPath}/bower_components/**/*`,
`typings/main.d.ts`
],
'./tsconfig.client.test.json');
});
gulp.task('inject:css', () => {
return gulp.src(paths.client.mainView)
.pipe(plugins.inject(
gulp.src(`${clientPath}/{app,components}/**/*.css`, {read: false})
.pipe(plugins.sort()),
{
starttag: '<!-- injector:css -->',
endtag: '<!-- endinjector -->',
transform: (filepath) => '<link rel="stylesheet" href="' + filepath.replace(`/${clientPath}/`, '').replace('/.tmp/', '') + '">'
}))
.pipe(gulp.dest(clientPath));
});
gulp.task('inject:less', () => {
return gulp.src(paths.client.mainStyle)
.pipe(plugins.inject(
gulp.src(_.union(paths.client.styles, ['!' + paths.client.mainStyle]), {read: false})
.pipe(plugins.sort()),
{
transform: (filepath) => {
let newPath = filepath
.replace(`/${clientPath}/app/`, '')
.replace(`/${clientPath}/components/`, '../components/')
.replace(/_(.*).less/, (match, p1, offset, string) => p1)
.replace('.less', '');
return `#import '${newPath}';`;
}
}))
.pipe(gulp.dest(`${clientPath}/app`));
});
// Install DefinitelyTyped TypeScript definition files
gulp.task('typings', () => {
return gulp.src("./typings.json")
.pipe(plugins.typings());
});
gulp.task('styles', () => {
return gulp.src(paths.client.mainStyle)
.pipe(styles())
.pipe(gulp.dest('.tmp/app'));
});
gulp.task('copy:constant', ['constant'], () => {
return gulp.src(`${clientPath}/app/app.constant.js`, { dot: true })
.pipe(gulp.dest('.tmp/app'));
})
gulp.task('transpile:client', ['typings', 'copy:constant'], () => {
return gulp.src(['client/{app,components}/**/!(*.spec|*.mock).ts', 'typings/main.d.ts'])
.pipe(plugins.sourcemaps.init())
.pipe(plugins.typescript()).js
.pipe(plugins.sourcemaps.write('.'))
.pipe(gulp.dest('.tmp'));
});
gulp.task('transpile:client:test', ['typings'], () => {
return gulp.src(['client/{app,components}/**/+(*.spec|*.mock).ts', 'typings/main.d.ts'])
.pipe(plugins.sourcemaps.init())
.pipe(plugins.typescript()).js
.pipe(plugins.sourcemaps.write('.'))
.pipe(gulp.dest('.tmp/test'));
});
gulp.task('transpile:server', () => {
return gulp.src(_.union(paths.server.scripts, paths.server.json))
.pipe(transpileServer())
.pipe(gulp.dest(`${paths.dist}/${serverPath}`));
});
gulp.task('lint:scripts', cb => runSequence(['lint:scripts:client', 'lint:scripts:server'], cb));
gulp.task('lint:scripts:client', () => {
return gulp.src(_.union(
paths.client.scripts,
_.map(paths.client.test, blob => '!' + blob),
[`!${clientPath}/app/app.constant.ts`]
))
.pipe(lintClientScripts());
});
gulp.task('lint:scripts:server', () => {
return gulp.src(_.union(paths.server.scripts, _.map(paths.server.test, blob => '!' + blob)))
.pipe(lintServerScripts());
});
gulp.task('lint:scripts:clientTest', () => {
return gulp.src(paths.client.test)
.pipe(lintClientScripts());
});
gulp.task('lint:scripts:serverTest', () => {
return gulp.src(paths.server.test)
.pipe(lintServerTestScripts());
});
gulp.task('jscs', () => {
return gulp.src(_.union(paths.client.scripts, paths.server.scripts))
.pipe(plugins.jscs())
.pipe(plugins.jscs.reporter());
});
gulp.task('clean:tmp', () => del(['.tmp/**/*'], {dot: true}));
gulp.task('start:client', cb => {
whenServerReady(() => {
open('http://localhost:' + config.port);
cb();
});
});
gulp.task('start:server', () => {
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
config = require(`./${serverPath}/config/environment`);
nodemon(`-w ${serverPath} ${serverPath}`)
.on('log', onServerLog);
});
gulp.task('start:server:prod', () => {
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
config = require(`./${paths.dist}/${serverPath}/config/environment`);
nodemon(`-w ${paths.dist}/${serverPath} ${paths.dist}/${serverPath}`)
.on('log', onServerLog);
});
gulp.task('start:inspector', () => {
gulp.src([])
.pipe(plugins.nodeInspector());
});
gulp.task('start:server:debug', () => {
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
config = require(`./${serverPath}/config/environment`);
nodemon(`-w ${serverPath} --debug-brk ${serverPath}`)
.on('log', onServerLog);
});
gulp.task('watch', () => {
var testFiles = _.union(paths.client.test, paths.server.test.unit, paths.server.test.integration);
plugins.livereload.listen();
plugins.watch(paths.client.styles, () => { //['inject:less']
gulp.src(paths.client.mainStyle)
.pipe(plugins.plumber())
.pipe(styles())
.pipe(gulp.dest('.tmp/app'))
.pipe(plugins.livereload());
});
plugins.watch(paths.client.views)
.pipe(plugins.plumber())
.pipe(plugins.livereload());
gulp.watch(paths.client.scripts, ['transpile:client']);// gulp.watch(paths.client.scripts, ['lint:scripts:client', 'transpile:client']);
plugins.watch(_.union(paths.server.scripts, testFiles))
.pipe(plugins.plumber())
.pipe(lintServerScripts())
.pipe(plugins.livereload());
gulp.watch('bower.json', ['wiredep:client']);
});
gulp.task('serve', cb => {
runSequence(['clean:tmp', 'constant', 'env:all', 'typings'],
['inject'],// ['lint:scripts', 'inject'],
['wiredep:client'],
['transpile:client', 'styles'],
['start:server', 'start:client'],
'watch',
cb);
});
gulp.task('serve:dist', cb => {
runSequence(
'build',
'env:all',
'env:prod',
['start:server:prod', 'start:client'],
cb);
});
gulp.task('serve:debug', cb => {
runSequence(['clean:tmp', 'constant', 'typings'],
['lint:scripts', 'inject'],
['wiredep:client'],
['transpile:client', 'styles'],
'start:inspector',
['start:server:debug', 'start:client'],
'watch',
cb);
});
gulp.task('test', cb => {
return runSequence('test:server', 'test:client', cb);
});
gulp.task('test:server', cb => {
runSequence(
'env:all',
'env:test',
'mocha:unit',
'mocha:integration',
'mocha:coverage',
cb);
});
gulp.task('mocha:unit', () => {
return gulp.src(paths.server.test.unit)
.pipe(mocha());
});
gulp.task('mocha:integration', () => {
return gulp.src(paths.server.test.integration)
.pipe(mocha());
});
gulp.task('test:client', ['wiredep:test', 'constant', 'transpile:client', 'transpile:client:test'], (done) => {
new KarmaServer({
configFile: `${__dirname}/${paths.karma}`,
singleRun: true
}, done).start();
});
// inject bower components
gulp.task('wiredep:client', () => {
return gulp.src(paths.client.mainView)
.pipe(wiredep({
exclude: [
/bootstrap.js/,
'/json3/',
'/es5-shim/',
/font-awesome\.css/,
/bootstrap\.css/
],
ignorePath: clientPath
}))
.pipe(gulp.dest(`${clientPath}/`));
});
gulp.task('wiredep:test', () => {
return gulp.src(paths.karma)
.pipe(wiredep({
exclude: [
/bootstrap.js/,
'/json3/',
'/es5-shim/',
/font-awesome\.css/,
/bootstrap\.css/
],
devDependencies: true
}))
.pipe(gulp.dest('./'));
});
/********************
* Build
********************/
//FIXME: looks like font-awesome isn't getting loaded
gulp.task('build', cb => {
runSequence(
[
'clean:dist',
'clean:tmp'
],
'inject',
'wiredep:client',
'typings',
[
'transpile:client',
'transpile:server'
],
[
'build:images',
'copy:extras',
'copy:fonts',
'copy:assets',
'copy:server',
'build:client'
],
cb);
});
gulp.task('clean:dist', () => del([`${paths.dist}/!(.git*|.openshift|Procfile)**`], {dot: true}));
gulp.task('build:client', ['styles', 'html', 'constant', 'build:images'], () => {
var manifest = gulp.src(`${paths.dist}/${clientPath}/assets/rev-manifest.json`);
var appFilter = plugins.filter('**/app.js', {restore: true});
var jsFilter = plugins.filter('**/*.js', {restore: true});
var cssFilter = plugins.filter('**/*.css', {restore: true});
var htmlBlock = plugins.filter(['**/*.!(html)'], {restore: true});
return gulp.src(paths.client.mainView)
.pipe(plugins.useref())
.pipe(appFilter)
.pipe(plugins.addSrc.append('.tmp/templates.js'))
.pipe(plugins.concat('app/app.js'))
.pipe(appFilter.restore)
.pipe(jsFilter)
.pipe(plugins.ngAnnotate())
.pipe(plugins.uglify())
.pipe(jsFilter.restore)
.pipe(cssFilter)
.pipe(plugins.cleanCss({
processImportFrom: ['!fonts.googleapis.com']
}))
.pipe(cssFilter.restore)
.pipe(htmlBlock)
.pipe(plugins.rev())
.pipe(htmlBlock.restore)
.pipe(plugins.revReplace({manifest}))
.pipe(gulp.dest(`${paths.dist}/${clientPath}`));
});
gulp.task('html', function() {
return gulp.src(`${clientPath}/{app,components}/**/*.html`)
.pipe(plugins.angularTemplatecache({
module: 'intranetApp'
}))
.pipe(gulp.dest('.tmp'));
});
gulp.task('constant', function() {
let sharedConfig = require(`./${serverPath}/config/environment/shared`);
return plugins.ngConstant({
name: 'intranetApp.constants',
deps: [],
wrap: true,
stream: true,
constants: { appConfig: sharedConfig }
})
.pipe(plugins.rename({
basename: 'app.constant'
}))
.pipe(gulp.dest(`${clientPath}/app/`))
});
gulp.task('build:images', () => {
return gulp.src(paths.client.images)
.pipe(plugins.imagemin({
optimizationLevel: 5,
progressive: true,
interlaced: true
}))
.pipe(plugins.rev())
.pipe(gulp.dest(`${paths.dist}/${clientPath}/assets/images`))
.pipe(plugins.rev.manifest(`${paths.dist}/${clientPath}/assets/rev-manifest.json`, {
base: `${paths.dist}/${clientPath}/assets`,
merge: true
}))
.pipe(gulp.dest(`${paths.dist}/${clientPath}/assets`));
});
gulp.task('copy:extras', () => {
return gulp.src([
`${clientPath}/favicon.ico`,
`${clientPath}/robots.txt`,
`${clientPath}/.htaccess`
], { dot: true })
.pipe(gulp.dest(`${paths.dist}/${clientPath}`));
});
gulp.task('copy:fonts', () => {
return gulp.src(`${clientPath}/bower_components/{bootstrap,font-awesome}/fonts/**/*`, { dot: true })
.pipe(gulp.dest(`${paths.dist}/${clientPath}/bower_components`));
});
gulp.task('copy:assets', () => {
return gulp.src([paths.client.assets, '!' + paths.client.images])
.pipe(gulp.dest(`${paths.dist}/${clientPath}/assets`));
});
gulp.task('copy:server', () => {
return gulp.src([
'package.json',
'bower.json',
'.bowerrc'
], {cwdbase: true})
.pipe(gulp.dest(paths.dist));
});
gulp.task('coverage:pre', () => {
return gulp.src(paths.server.scripts)
// Covering files
.pipe(plugins.istanbul({
instrumenter: Instrumenter, // Use the isparta instrumenter (code coverage for ES6)
includeUntested: true
}))
// Force `require` to return covered files
.pipe(plugins.istanbul.hookRequire());
});
gulp.task('coverage:unit', () => {
return gulp.src(paths.server.test.unit)
.pipe(mocha())
.pipe(istanbul())
// Creating the reports after tests ran
});
gulp.task('coverage:integration', () => {
return gulp.src(paths.server.test.integration)
.pipe(mocha())
.pipe(istanbul())
// Creating the reports after tests ran
});
gulp.task('mocha:coverage', cb => {
runSequence('coverage:pre',
'env:all',
'env:test',
'coverage:unit',
'coverage:integration',
cb);
});
// Downloads the selenium webdriver
gulp.task('webdriver_update', webdriver_update);
gulp.task('test:e2e', ['env:all', 'env:test', 'start:server', 'webdriver_update'], cb => {
gulp.src(paths.client.e2e)
.pipe(protractor({
configFile: 'protractor.conf.js',
})).on('error', err => {
console.log(err)
}).on('end', () => {
process.exit();
});
});
/********************
* Grunt ported tasks
********************/
grunt.initConfig({
buildcontrol: {
options: {
dir: paths.dist,
commit: true,
push: true,
connectCommits: false,
message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
},
heroku: {
options: {
remote: 'heroku',
branch: 'master'
}
},
openshift: {
options: {
remote: 'openshift',
branch: 'master'
}
}
}
});
grunt.loadNpmTasks('grunt-build-control');
gulp.task('buildcontrol:heroku', function(done) {
grunt.tasks(
['buildcontrol:heroku'], //you can add more grunt tasks in this array
{gruntfile: false}, //don't look for a Gruntfile - there is none. :-)
function() {done();}
);
});
gulp.task('buildcontrol:openshift', function(done) {
grunt.tasks(
['buildcontrol:openshift'], //you can add more grunt tasks in this array
{gruntfile: false}, //don't look for a Gruntfile - there is none. :-)
function() {done();}
);
});
In case anyone stumbles on the same issue... the gulpfile actually does find files in places that aren't really standard (like inside subfolders of places where they'd normally be at). My problem was that I specified the dot (.) in the beginning. When I changed the paths to components/modal/someFolder/someFile.html it worked fine. The relative path I initially had gets messed up in production.