Move to webpack

Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
Julius Härtl
2018-01-22 14:19:02 +01:00
parent e94986744d
commit 24f4f84eb6
43 changed files with 184 additions and 218 deletions

2
.gitignore vendored
View File

@@ -1,9 +1,11 @@
js/node_modules/* js/node_modules/*
js/vendor/ js/vendor/
js/public/ js/public/
js/build/
js/package-lock.json js/package-lock.json
build/ build/
css/style.css css/style.css
css/vendor.css
tests/integration/vendor/ tests/integration/vendor/
tests/integration/composer.lock tests/integration/composer.lock
vendor/ vendor/

View File

@@ -19,19 +19,17 @@ clean-build:
clean-dist: clean-dist:
rm -rf js/node_modules rm -rf js/node_modules
rm -rf js/vendor
install-deps: install-deps:
cd js && npm install --deps cd js && npm install
cd js && ./node_modules/.bin/bower install
build: build-js build: build-js
build-js: install-deps build-js: install-deps
cd js && ./node_modules/.bin/grunt build cd js && ./node_modules/.bin/webpack --config webpack.prod.config.js
watch: watch:
cd js && ./node_modules/.bin/grunt watch cd js && ./node_modules/.bin/webpack --config webpack.dev.config.js --watch
# appstore: clean install-deps # appstore: clean install-deps
appstore: clean-build build appstore: clean-build build

View File

@@ -1,3 +0,0 @@
{
"directory": "vendor"
}

View File

@@ -1,124 +0,0 @@
/*
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* global module */
module.exports = function(grunt) {
'use strict';
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-wrap');
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-phpunit');
grunt.initConfig({
meta: {
pkg: grunt.file.readJSON('package.json'),
version: '<%= meta.pkg.version %>',
configJS: 'config/',
buildJS: [
'app/**/*.js',
'controller/**/*.js',
'filters/**/*.js',
'directive/**/*.js',
'service/**/*.js'
],
productionJS: 'public/',
testsJS: '../tests/js/'
},
concat: {
options: {
stripBanners: true
},
dist: {
src: ['<%= meta.buildJS %>'],
dest: '<%= meta.productionJS %>app.js'
}
},
wrap: {
app: {
src: ['<%= meta.productionJS %>app.js'],
dest: '<%= meta.productionJS %>app.js',
option: {
wrapper: [
'(function(angular, $, oc_requesttoken, undefined){\n\n\'use strict\';\n\n',
'\n})(angular, jQuery, oc_requesttoken);'
]
}
}
},
jshint: {
files: [
'Gruntfile.js',
'<%= meta.buildJS %>**/*.js',
'<%= meta.testsJS %>**/*.js'
],
options: {
jshintrc: '.jshintrc',
reporter: require('jshint-stylish')
}
},
watch: {
concat: {
files: ['<%=meta.buildJS%>'],
options: {
livereload: true
},
tasks: ['build']
}
},
phpunit: {
classes: {
dir: '../tests/unit'
},
options: {
bootstrap: '../tests/bootstrap.php',
colors: true
}
},
karma: {
unit: {
configFile: '<%= meta.testsJS %>config/karma.js'
},
continuous: {
configFile: '<%= meta.testsJS %>config/karma.js',
browsers: ['Firefox'],
singleRun: true,
reporters: ['progress']
}
},
});
// make tasks available under simpler commands
grunt.registerTask('build', ['jshint', 'concat', 'wrap']);
grunt.registerTask('js-unit', ['karma:continuous']);
};

View File

@@ -41,13 +41,18 @@ angular.module('markdown', [])
}; };
}]); }]);
import uirouter from '@uirouter/angularjs';
import ngsanitize from 'angular-sanitize';
import angularuiselect from 'ui-select';
import ngsortable from 'ng-sortable';
import md from 'angular-markdown-it';
import nganimate from 'angular-animate';
var app = angular.module('Deck', [ var app = angular.module('Deck', [
'ngRoute', ngsanitize,
'ngSanitize', uirouter,
'ui.router', angularuiselect,
'ui.select', ngsortable, md, nganimate
'as.sortable',
'mdMarkdownIt',
'ngAnimate'
]); ]);
export default app;

View File

@@ -22,18 +22,21 @@
/* global app oc_requesttoken markdownitLinkTarget */ /* global app oc_requesttoken markdownitLinkTarget */
app.config(function ($provide, $routeProvider, $interpolateProvider, $httpProvider, $urlRouterProvider, $stateProvider, $compileProvider, markdownItConverterProvider) { import app from './App.js';
import md from 'angular-markdown-it';
import markdownitLinkTarget from 'markdown-it-link-target';
app.config(function ($provide, $interpolateProvider, $httpProvider, $urlRouterProvider, $stateProvider, $compileProvider, markdownItConverterProvider) {
'use strict'; 'use strict';
$httpProvider.defaults.headers.common.requesttoken = oc_requesttoken; $httpProvider.defaults.headers.common.requesttoken = oc_requesttoken;
$compileProvider.debugInfoEnabled(true); $compileProvider.debugInfoEnabled(true);
markdownItConverterProvider.config({ markdownItConverterProvider.use(markdownitLinkTarget, {
breaks: true, breaks: true,
linkify: true, linkify: true,
xhtmlOut: true xhtmlOut: true
}); });
markdownItConverterProvider.use(markdownitLinkTarget);
$urlRouterProvider.otherwise('/'); $urlRouterProvider.otherwise('/');

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from './App.js';
/* global Snap */ /* global Snap */
app.run(function ($document, $rootScope, $transitions, BoardService) { app.run(function ($document, $rootScope, $transitions, BoardService) {

View File

@@ -1,29 +0,0 @@
{
"name": "deck",
"version": "0.0.1",
"dependencies": {
"angular": "~1.6.1",
"angular-route": "~1.6.1",
"angular-mocks": "~1.6.1",
"angular-sanitize": "~1.6.1",
"angular-animate": "~1.6.1",
"ng-sortable": "1.3.8",
"jquery": "3.2.x",
"es6-shim": "~0.*",
"js-url": "~2.*",
"angular-ui-select": "~0.19.6",
"angular-markdown-it": "~0.6.1",
"angular-ui-router": "~1.0.0",
"markdown-it-link-target": "~1.0.1",
"jquery-timepicker": "883bb2cd94"
},
"license": "AGPL-3.0",
"private": true,
"ignore": [
"'**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View File

@@ -20,8 +20,10 @@
* *
*/ */
import app from '../app/App.js';
/** global: OC */ /** global: OC */
app.controller('AppController', function ($scope, $location, $http, $route, $log, $rootScope) { app.controller('AppController', function ($scope, $location, $http, $log, $rootScope) {
$rootScope.sidebar = { $rootScope.sidebar = {
show: false show: false
}; };

View File

@@ -20,6 +20,7 @@
* *
*/ */
import app from '../app/App.js';
/* global oc_defaults OC */ /* global oc_defaults OC */
app.controller('BoardController', function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService, LabelService, $state, $transitions, $filter) { app.controller('BoardController', function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService, LabelService, $state, $transitions, $filter) {

View File

@@ -21,6 +21,7 @@
*/ */
/* global app moment */ /* global app moment */
import app from '../app/App.js';
app.controller('CardController', function ($scope, $rootScope, $routeParams, $location, $stateParams, $interval, $timeout, $filter, BoardService, CardService, StackService, StatusService) { app.controller('CardController', function ($scope, $rootScope, $routeParams, $location, $stateParams, $interval, $timeout, $filter, BoardService, CardService, StackService, StatusService) {
$scope.sidebar = $rootScope.sidebar; $scope.sidebar = $rootScope.sidebar;

View File

@@ -22,7 +22,7 @@
/* global app angular */ /* global app angular */
app.controller('ListController', function ($scope, $location, $filter, BoardService, $element, $timeout, $stateParams, $state, StatusService) { var ListController = function ($scope, $location, $filter, BoardService, $element, $timeout, $stateParams, $state, StatusService) {
function calculateNewColor() { function calculateNewColor() {
var boards = BoardService.getAll(); var boards = BoardService.getAll();
@@ -193,5 +193,6 @@ app.controller('ListController', function ($scope, $location, $filter, BoardServ
}); });
}; };
}); };
export default ListController;

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.directive('appPopoverMenuUtils', function () { app.directive('appPopoverMenuUtils', function () {
'use strict'; 'use strict';

View File

@@ -20,6 +20,7 @@
* *
*/ */
import app from '../app/App.js';
// OwnCloud Click Handling // OwnCloud Click Handling
// https://doc.owncloud.org/server/8.0/developer_manual/app/css.html // https://doc.owncloud.org/server/8.0/developer_manual/app/css.html
app.directive('appNavigationEntryUtils', function () { app.directive('appNavigationEntryUtils', function () {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.directive('autofocusOnInsert', function () { app.directive('autofocusOnInsert', function () {
'use strict'; 'use strict';

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.directive('avatar', function() { app.directive('avatar', function() {
'use strict'; 'use strict';

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.directive('contactsmenudelete', function() { app.directive('contactsmenudelete', function() {
'use strict'; 'use strict';

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
/* global app */ /* global app */
/* gloabl t */ /* gloabl t */

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
// original idea from blockloop: http://stackoverflow.com/a/24090733 // original idea from blockloop: http://stackoverflow.com/a/24090733
app.directive('elastic', [ app.directive('elastic', [

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.directive('search', function ($document, $location) { app.directive('search', function ($document, $location) {
'use strict'; 'use strict';

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
/* global app */ /* global app */
/* global t */ /* global t */

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.filter('boardFilterAcl', function() { app.filter('boardFilterAcl', function() {
return function(boards) { return function(boards) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
// usage | cardFilter({ member: 'admin'}) // usage | cardFilter({ member: 'admin'})

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.filter('cardSearchFilter', function() { app.filter('cardSearchFilter', function() {
return function(cards, searchString) { return function(cards, searchString) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
/* global app */ /* global app */
/* global OC */ /* global OC */

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.filter('iconWhiteFilter', function () { app.filter('iconWhiteFilter', function () {
return function (hex) { return function (hex) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.filter('lightenColorFilter', function() { app.filter('lightenColorFilter', function() {
return function (hex) { return function (hex) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.filter('orderObjectBy', function(){ app.filter('orderObjectBy', function(){
return function(input, attribute) { return function(input, attribute) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.filter('textColorFilter', function () { app.filter('textColorFilter', function () {
return function (hex) { return function (hex) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
/* global app */ /* global app */
/* global angular */ /* global angular */

25
js/init.js Normal file
View File

@@ -0,0 +1,25 @@
'use strict';
// used for building a vendor stylesheet
import 'jquery-timepicker/jquery.timepicker.css';
import 'ng-sortable/dist/ng-sortable.css';
import angular from 'angular';
import app from './app/App.js';
import './app/Config.js';
import './app/Run.js';
import ListController from 'controller/ListController.js';
app.controller('ListController', ListController)
// require all the js files from subdirectories
var context = require.context(".", true, /(controller|service|filters|directive)\/(.*)\.js$/);
context.keys().forEach(function (key) {
context(key);
});

View File

@@ -5,19 +5,30 @@
"directories": { "directories": {
"test": "tests" "test": "tests"
}, },
"dependencies": {}, "dependencies": {
"@uirouter/angularjs": "^1.0.13",
"angular": "^1.6.8",
"angular-animate": "^1.6.8",
"angular-markdown-it": "^0.6.1",
"angular-sanitize": "^1.6.8",
"jquery": "^3.3.1",
"jquery-timepicker": "^1.3.3",
"markdown-it-link-target": "^1.0.2",
"ng-sortable": "^1.3.8",
"ui-select": "^0.19.8"
},
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"bower": "^1.8.0", "bower": "^1.8.0",
"grunt": "^1.0.1", "css-loader": "^0.28.9",
"grunt-contrib-concat": "^1.0.1", "extract-text-webpack-plugin": "^3.0.2",
"grunt-contrib-jshint": "^1.1.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-karma": "^2.0.0",
"grunt-phpunit": "^0.3.6",
"grunt-wrap": "^0.3.0",
"jshint-stylish": "^2.2.1", "jshint-stylish": "^2.2.1",
"karma": "^1.4.1", "karma": "^1.4.1",
"node-sass": "^4.5.3" "node-sass": "^4.5.3",
"style-loader": "^0.19.1",
"webpack": "^3.10.0",
"webpack-merge": "^4.1.1"
}, },
"scripts": { "scripts": {
"test": "echo \"Warning: no test specified\" && exit 0" "test": "echo \"Warning: no test specified\" && exit 0"

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
/** global: oc_defaults */ /** global: oc_defaults */
app.factory('ApiService', function ($http, $q) { app.factory('ApiService', function ($http, $q) {

View File

@@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
/* global app OC */ /* global app OC */
app.factory('BoardService', function (ApiService, $http, $q) { app.factory('BoardService', function (ApiService, $http, $q) {
var BoardService = function ($http, ep, $q) { var BoardService = function ($http, ep, $q) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.factory('CardService', function (ApiService, $http, $q) { app.factory('CardService', function (ApiService, $http, $q) {
var CardService = function ($http, ep, $q) { var CardService = function ($http, ep, $q) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.factory('LabelService', function (ApiService, $http, $q) { app.factory('LabelService', function (ApiService, $http, $q) {
var LabelService = function ($http, ep, $q) { var LabelService = function ($http, ep, $q) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.factory('StackService', function (ApiService, $http, $q) { app.factory('StackService', function (ApiService, $http, $q) {
var StackService = function ($http, ep, $q) { var StackService = function ($http, ep, $q) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import app from '../app/App.js';
app.factory('StatusService', function () { app.factory('StatusService', function () {
// Status Helper // Status Helper

54
js/webpack.config.js Normal file
View File

@@ -0,0 +1,54 @@
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
deck: './init.js',
},
output: {
filename: '[name].js',
path: __dirname + '/build'
},
resolve: {
modules: [path.resolve(__dirname), 'node_modules'],
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: {
loader: 'css-loader',
options: {
minimize: true,
}
},
})
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
test: /(vendor\.js)+/i,
}),
// we do not uglify deck.js since there are no proper dependency annotations
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.js',
minChunks(module, count) {
var context = module.context;
return context && context.indexOf('node_modules') >= 0;
},
}),
new ExtractTextPlugin({
filename: "../../css/vendor.css",
allChunks: true
}),
]
};

6
js/webpack.dev.config.js Normal file
View File

@@ -0,0 +1,6 @@
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.js');
module.exports = merge(baseConfig, {
devtool: 'eval'
});

13
js/webpack.prod.config.js Normal file
View File

@@ -0,0 +1,13 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.js');
module.exports = merge(baseConfig, {
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
]
});

View File

@@ -4,6 +4,10 @@ exclude = [
".git", ".git",
"js/node_modules", "js/node_modules",
"js/tests", "js/tests",
"js/controller",
"js/directive",
"js/filters",
"js/service",
"js/bower.json", "js/bower.json",
"js/.bowerrc", "js/.bowerrc",
"js/.jshintrc", "js/.jshintrc",

View File

@@ -23,39 +23,11 @@
use OCP\Util; use OCP\Util;
Util::addStyle('deck', '../js/vendor/ng-sortable/dist/ng-sortable.min'); Util::addStyle('deck', 'vendor');
Util::addStyle('deck', '../js/vendor/jquery-timepicker/jquery.ui.timepicker');
Util::addStyle('deck', 'style'); Util::addStyle('deck', 'style');
Util::addScript('deck', 'vendor/angular/angular.min'); Util::addScript('deck', 'build/vendor');
Util::addScript('deck', 'vendor/angular-route/angular-route.min'); Util::addScript('deck', 'build/deck');
Util::addScript('deck', 'vendor/angular-sanitize/angular-sanitize.min');
Util::addScript('deck', 'vendor/angular-animate/angular-animate.min');
Util::addScript('deck', 'vendor/angular-ui-router/release/angular-ui-router.min');
Util::addScript('deck', 'vendor/ng-sortable/dist/ng-sortable.min');
Util::addScript('deck', 'vendor/angular-ui-select/dist/select.min');
Util::addScript('deck', 'vendor/markdown-it/dist/markdown-it.min');
Util::addScript('deck', 'vendor/angular-markdown-it/dist/ng-markdownit.min');
Util::addScript('deck', 'vendor/markdown-it-link-target/dist/markdown-it-link-target.min');
Util::addScript('deck', 'vendor/jquery-timepicker/jquery.ui.timepicker');
if(!\OC::$server->getConfig()->getSystemValue('debug', false)) {
Util::addScript('deck', 'public/app');
} else {
// Load seperate JS files when debug mode is enabled
$js = [
'app' => ['App', 'Config', 'Run'],
'controller' => ['AppController', 'BoardController', 'CardController', 'ListController'],
'directive' => ['appnavigationentryutils', 'appPopoverMenuUtils', 'autofocusoninsert', 'avatar', 'contactsmenudelete', 'elastic', 'search', 'datepicker', 'timepicker'],
'filters' => ['boardFilterAcl', 'cardFilter', 'cardSearchFilter', 'iconWhiteFilter', 'lightenColorFilter', 'orderObjectBy', 'dateFilters', 'textColorFilter', 'withoutAssignedUsers'],
'service' => ['ApiService', 'BoardService', 'CardService', 'LabelService', 'StackService', 'StatusService'],
];
foreach($js as $folder=>$files) {
foreach ($files as $file) {
Util::addScript('deck', $folder.'/'.$file);
}
}
}
?> ?>
<div id="app" class="app-deck" data-ng-app="Deck" ng-controller="AppController" ng-cloak> <div id="app" class="app-deck" data-ng-app="Deck" ng-controller="AppController" ng-cloak>