Merge remote-tracking branch 'origin/feature/new-content-block-creation-workflow' into develop
This commit is contained in:
commit
a626fd9d04
|
|
@ -1,3 +1,13 @@
|
|||
{
|
||||
"schemaPath": "server/schema.graphql"
|
||||
"projects": {
|
||||
"private": {
|
||||
"schemaPath": "./server/schema.graphql",
|
||||
"includes": ["./client/src/graphql/**"],
|
||||
"excludes": ["./client/src/graphql/gql/public-client/**"]
|
||||
},
|
||||
"public": {
|
||||
"schemaPath": "./server/schema-public.graphql",
|
||||
"includes": ["./client/src/graphql/gql/public-client/*.gql"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
schema: 'server/schema.graphql'
|
||||
4
Pipfile
4
Pipfile
|
|
@ -42,6 +42,8 @@ ipython = "*"
|
|||
requests = "*"
|
||||
unittest-xml-reporting = "*"
|
||||
django-silk = "*"
|
||||
wagtail-autocomplete = "*"
|
||||
# todo: @django3-update
|
||||
# wagtail-autocomplete = "*"
|
||||
wagtail-autocomplete = "==0.6.3"
|
||||
jedi = "==0.17.2"
|
||||
Authlib = "*"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
[[source]]
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[requires]
|
||||
python_version = "3.8"
|
||||
|
||||
[dev-packages]
|
||||
awscli = "*"
|
||||
ipdb = "*"
|
||||
coverage = "*"
|
||||
django-silk = "*"
|
||||
|
||||
[packages]
|
||||
factory-boy = "==2.11.0"
|
||||
wagtail_factories = "==2.0.0"
|
||||
django = "==3.2"
|
||||
whitenoise = "~=5.3"
|
||||
psycopg2 = "==2.8.6"
|
||||
gunicorn = "==19.7.1"
|
||||
python-dotenv = "==0.13.0"
|
||||
dj-database-url = "==0.4.1"
|
||||
raven = "==6.9.0"
|
||||
django-extensions = "==1.9.8"
|
||||
graphene-django = "==2.15.0"
|
||||
django-filter = "~=21.1"
|
||||
djangorestframework = "~=3.8"
|
||||
pillow = "==5.0.0"
|
||||
wagtail = "~=2.15"
|
||||
django-cors-headers = "~=3.0"
|
||||
django-storages = "*"
|
||||
boto3 = "*"
|
||||
django-compressor = "*"
|
||||
django-libsass = "*"
|
||||
bleach = "*"
|
||||
newrelic = "*"
|
||||
sentry-sdk = "==0.7.2"
|
||||
django-sendgrid-v5 = "==0.8.0"
|
||||
python-http-client = "==3.2.1"
|
||||
ipython = "*"
|
||||
requests = "*"
|
||||
unittest-xml-reporting = "*"
|
||||
django-silk = "*"
|
||||
wagtail-autocomplete = "*"
|
||||
jedi = "==0.17.2"
|
||||
Authlib = "*"
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -270,3 +270,8 @@ Command:
|
|||
```
|
||||
./bin/pg-backup-to-s3
|
||||
```
|
||||
|
||||
|
||||
# Note on component
|
||||
Our own components remain in kebap-case, imported components from third party libraries will be used in PascalCase.
|
||||
E.g. `<password-change-form/>` vs. `<ValidationProvider/>`
|
||||
|
|
|
|||
|
|
@ -3,23 +3,36 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
parser: '@typescript-eslint/parser',
|
||||
extraFileExtensions: ['.vue'],
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
globals: {
|
||||
process: "readonly"
|
||||
},
|
||||
extends: [
|
||||
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
|
||||
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
|
||||
'plugin:vue/recommended',
|
||||
// 'plugin:vue/recommended',
|
||||
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
|
||||
'standard'
|
||||
//'standard'
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended'
|
||||
],
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'vue'
|
||||
'vue',
|
||||
'@typescript-eslint'
|
||||
],
|
||||
overrides: [{
|
||||
files: ['*.ts','*.tsx'],
|
||||
rules: {
|
||||
'no-unused-vars': 'off'
|
||||
}
|
||||
}],
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
// allow async-await
|
||||
|
|
@ -49,6 +62,9 @@ module.exports = {
|
|||
'CONTENT'
|
||||
]
|
||||
}],
|
||||
"vue/multi-word-component-names": ["off", {
|
||||
"ignores": []
|
||||
}],
|
||||
'vue/order-in-components': ['error', {
|
||||
'order': [
|
||||
'el',
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ spinner.start()
|
|||
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
|
||||
if (err) throw err
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
spinner.stop()
|
||||
spinner.succeed()
|
||||
if (err) throw err
|
||||
process.stdout.write(stats.toString({
|
||||
colors: true,
|
||||
|
|
|
|||
|
|
@ -1,56 +1,10 @@
|
|||
'use strict'
|
||||
const path = require('path')
|
||||
const config = require('../config')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const packageConfig = require('../package.json')
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
const styleRule = (scss) => {
|
||||
const test = scss ? /\.scss$/ : /\.css$/;
|
||||
let use = [
|
||||
{
|
||||
loader: 'css-loader',
|
||||
// options: {importLoaders: scss ? 3 : 2}
|
||||
options: {
|
||||
sourceMap: isDev,
|
||||
importLoaders: scss ? 2 : 1
|
||||
}
|
||||
},
|
||||
'postcss-loader'
|
||||
];
|
||||
if (scss) {
|
||||
use = [
|
||||
...use,
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
data: process.env.THEME ? `@import "styles/themes/_${process.env.THEME}.scss";` : '',
|
||||
sourceMap: isDev
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if (!isDev) {
|
||||
return {
|
||||
test,
|
||||
loader: ExtractTextPlugin.extract({
|
||||
use,
|
||||
fallback: 'vue-style-loader'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
test,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
...use
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const assetsPath = (_path) => {
|
||||
const assetsSubDirectory = isDev
|
||||
? config.dev.assetsSubDirectory
|
||||
|
|
@ -78,7 +32,6 @@ const createNotifierCallback = () => {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
styleRule,
|
||||
isDev,
|
||||
assetsPath,
|
||||
createNotifierCallback
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
'use strict'
|
||||
const config = require('../config')
|
||||
|
||||
module.exports = {
|
||||
// cacheBusting: config.dev.cacheBusting,
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
'use strict';
|
||||
const path = require('path');
|
||||
const config = require('../config');
|
||||
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
|
||||
const {VueLoaderPlugin} = require('vue-loader');
|
||||
|
||||
const {isDev, styleRule, assetsPath} = require('./utils');
|
||||
const {isDev, assetsPath} = require('./utils');
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, '..', dir);
|
||||
|
|
@ -37,13 +38,29 @@ module.exports = {
|
|||
? config.dev.assetsPublicPath
|
||||
: config.build.assetsPublicPath,
|
||||
},
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: 'all'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json', '.gql', '.graphql', '.scss'],
|
||||
alias: {
|
||||
'@': resolve('src'),
|
||||
styles: resolve('src/styles'),
|
||||
gql: resolve('src/graphql/gql'),
|
||||
// vue: '@vue/compat',
|
||||
},
|
||||
// we probably don't need this anymore
|
||||
// fallback: {
|
||||
// // used to be in node: {setImmediate: false,...}
|
||||
// setImmediate: false,
|
||||
// dgram: false,
|
||||
// fs: false,
|
||||
// net: false,
|
||||
// tls: false,
|
||||
// child_process: false,
|
||||
// },
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
|
@ -58,6 +75,11 @@ module.exports = {
|
|||
img: 'src',
|
||||
image: 'xlink:href',
|
||||
},
|
||||
compilerOptions: {
|
||||
compatConfig: {
|
||||
MODE: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -105,23 +127,33 @@ module.exports = {
|
|||
name: assetsPath('fonts/[name].[hash:7].[ext]'),
|
||||
},
|
||||
},
|
||||
styleRule(false), // css rule
|
||||
styleRule(true), // sass rule
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
isDev ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
'sass-loader'
|
||||
]
|
||||
}
|
||||
// styleRule(false), // css rule
|
||||
// styleRule(true), // sass rule
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
],
|
||||
node: {
|
||||
// prevent webpack from injecting useless setImmediate polyfill because Vue
|
||||
// source contains it (although only uses it if it's native).
|
||||
setImmediate: false,
|
||||
// prevent webpack from injecting mocks to Node native modules
|
||||
// that does not make sense for the client
|
||||
dgram: 'empty',
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty',
|
||||
},
|
||||
|
||||
// node: {
|
||||
// // prevent webpack from injecting useless setImmediate polyfill because Vue
|
||||
// // source contains it (although only uses it if it's native).
|
||||
// setImmediate: false,
|
||||
// // prevent webpack from injecting mocks to Node native modules
|
||||
// // that does not make sense for the client
|
||||
// dgram: 'empty',
|
||||
// fs: 'empty',
|
||||
// net: 'empty',
|
||||
// tls: 'empty',
|
||||
// child_process: 'empty',
|
||||
// },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,93 +1,99 @@
|
|||
'use strict'
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const path = require('path')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const portfinder = require('portfinder')
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const webpack = require('webpack');
|
||||
const config = require('../config');
|
||||
const path = require('path');
|
||||
const baseWebpackConfig = require('./webpack.base.conf');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
const portfinder = require('portfinder');
|
||||
const {merge} = require('webpack-merge');
|
||||
|
||||
const HOST = process.env.HOST
|
||||
const PORT = process.env.PORT && Number(process.env.PORT)
|
||||
const HOST = process.env.HOST;
|
||||
const PORT = process.env.PORT && Number(process.env.PORT);
|
||||
|
||||
const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
// cheap-module-eval-source-map is faster for development
|
||||
devtool: config.dev.devtool,
|
||||
|
||||
mode: 'development',
|
||||
// these devServer options should be customized in /config/index.js
|
||||
devServer: {
|
||||
clientLogLevel: 'warning',
|
||||
client: {
|
||||
logging: 'warn',
|
||||
overlay: config.dev.errorOverlay ? {errors: true, warnings: false} : false,
|
||||
progress: true,
|
||||
|
||||
},
|
||||
|
||||
historyApiFallback: {
|
||||
rewrites: [
|
||||
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
|
||||
{from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html')},
|
||||
],
|
||||
},
|
||||
hot: true,
|
||||
contentBase: false, // since we use CopyWebpackPlugin.
|
||||
compress: true,
|
||||
host: HOST || config.dev.host,
|
||||
port: PORT || config.dev.port,
|
||||
open: config.dev.autoOpenBrowser,
|
||||
overlay: config.dev.errorOverlay
|
||||
? { warnings: false, errors: true }
|
||||
: false,
|
||||
publicPath: config.dev.assetsPublicPath,
|
||||
// publicPath: config.dev.assetsPublicPath,
|
||||
proxy: config.dev.proxyTable,
|
||||
quiet: true, // necessary for FriendlyErrorsPlugin
|
||||
watchOptions: {
|
||||
poll: config.dev.poll,
|
||||
}
|
||||
// quiet: true, // necessary for FriendlyErrorsPlugin
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../config/dev.env')
|
||||
'process.env': require('../config/dev.env'),
|
||||
// bundler feature flags https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags
|
||||
__VUE_OPTIONS_API__: true, // default, but explicit
|
||||
__VUE_PROD_DEVTOOLS__: false, // default, but explicit
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
...require('../config/dev.env')
|
||||
...require('../config/dev.env'),
|
||||
}),
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.dev.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
globOptions: {
|
||||
ignore: ['.*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'disabled' // do nothing by default, but be able to generate stats with --profile
|
||||
})
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = new Promise((resolve, reject) => {
|
||||
portfinder.basePort = process.env.PORT || config.dev.port
|
||||
portfinder.basePort = process.env.PORT || config.dev.port;
|
||||
portfinder.getPort((err, port) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
} else {
|
||||
// publish the new Port, necessary for e2e tests
|
||||
process.env.PORT = port
|
||||
process.env.PORT = port;
|
||||
// add port to devServer config
|
||||
devWebpackConfig.devServer.port = port
|
||||
devWebpackConfig.devServer.port = port;
|
||||
|
||||
// Add FriendlyErrorsPlugin
|
||||
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
|
||||
compilationSuccessInfo: {
|
||||
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
|
||||
},
|
||||
onErrors: config.dev.notifyOnErrors
|
||||
? utils.createNotifierCallback()
|
||||
: undefined
|
||||
}))
|
||||
// todo: seems to not be maintained anymore, disable for now
|
||||
// devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
|
||||
// compilationSuccessInfo: {
|
||||
// messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
|
||||
// },
|
||||
// onErrors: config.dev.notifyOnErrors
|
||||
// ? utils.createNotifierCallback()
|
||||
// : undefined,
|
||||
// }));
|
||||
|
||||
resolve(devWebpackConfig)
|
||||
resolve(devWebpackConfig);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,54 +1,43 @@
|
|||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const utils = require('./utils');
|
||||
const webpack = require('webpack');
|
||||
const config = require('../config');
|
||||
const {merge} = require('webpack-merge');
|
||||
const baseWebpackConfig = require('./webpack.base.conf');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
|
||||
const env = require('../config/prod.env')
|
||||
const env = require('../config/prod.env');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
const webpackConfig = merge(baseWebpackConfig, {
|
||||
devtool: config.build.productionSourceMap ? config.build.devtool : false,
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
|
||||
},
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': env
|
||||
}),
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
},
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
parallel: true
|
||||
'process.env': env,
|
||||
// bundler feature flags https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags
|
||||
__VUE_OPTIONS_API__: true, // default, but explicit
|
||||
__VUE_PROD_DEVTOOLS__: false, // default, but explicit
|
||||
}),
|
||||
// extract css into its own file
|
||||
new ExtractTextPlugin({
|
||||
new MiniCssExtractPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash].css'),
|
||||
// Setting the following option to `false` will not extract CSS from codesplit chunks.
|
||||
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
|
||||
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
|
||||
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
|
||||
allChunks: true,
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: config.build.productionSourceMap
|
||||
? { safe: true, map: { inline: false } }
|
||||
: { safe: true }
|
||||
? {safe: true, map: {inline: false}}
|
||||
: {safe: true},
|
||||
}),
|
||||
// generate dist index.html with correct asset hash for caching.
|
||||
// you can customize output by editing /index.html
|
||||
|
|
@ -56,65 +45,70 @@ const webpackConfig = merge(baseWebpackConfig, {
|
|||
new HtmlWebpackPlugin({
|
||||
filename: config.build.index,
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
...require('../config/prod.env'),
|
||||
minify: {
|
||||
removeComments: true,
|
||||
minify: { // defaults from https://github.com/jantimon/html-webpack-plugin#minification
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
keepClosingSlash: true,
|
||||
removeComments: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
useShortDoctype: true,
|
||||
},
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
chunksSortMode: 'auto',
|
||||
}),
|
||||
// keep module.id stable when vendor modules does not change
|
||||
new webpack.HashedModuleIdsPlugin(),
|
||||
// enable scope hoisting
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
new webpack.ids.HashedModuleIdsPlugin(),
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks (module) {
|
||||
// any required modules inside node_modules are extracted to vendor
|
||||
return (
|
||||
module.resource &&
|
||||
/\.js$/.test(module.resource) &&
|
||||
module.resource.indexOf(
|
||||
path.join(__dirname, '../node_modules')
|
||||
) === 0
|
||||
)
|
||||
}
|
||||
}),
|
||||
// todo: https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693
|
||||
// todo: do we need this? probably default is fine
|
||||
// new webpack.optimize.CommonsChunkPlugin({
|
||||
// name: 'vendor',
|
||||
// minChunks (module) {
|
||||
// // any required modules inside node_modules are extracted to vendor
|
||||
// return (
|
||||
// module.resource &&
|
||||
// /\.js$/.test(module.resource) &&
|
||||
// module.resource.indexOf(
|
||||
// path.join(__dirname, '../node_modules')
|
||||
// ) === 0
|
||||
// )
|
||||
// }
|
||||
// }),
|
||||
// extract webpack runtime and module manifest to its own file in order to
|
||||
// prevent vendor hash from being updated whenever app bundle is updated
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'manifest',
|
||||
minChunks: Infinity
|
||||
}),
|
||||
// new webpack.optimize.CommonsChunkPlugin({
|
||||
// name: 'manifest',
|
||||
// minChunks: Infinity
|
||||
// }),
|
||||
// This instance extracts shared chunks from code splitted chunks and bundles them
|
||||
// in a separate chunk, similar to the vendor chunk
|
||||
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'app',
|
||||
async: 'vendor-async',
|
||||
children: true,
|
||||
minChunks: 3
|
||||
}),
|
||||
// new webpack.optimize.CommonsChunkPlugin({
|
||||
// name: 'app',
|
||||
// async: 'vendor-async',
|
||||
// children: true,
|
||||
// minChunks: 3
|
||||
// }),
|
||||
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
globOptions: {
|
||||
ignore: ['.*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
if (config.build.productionGzip) {
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin')
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin');
|
||||
|
||||
webpackConfig.plugins.push(
|
||||
new CompressionWebpackPlugin({
|
||||
|
|
@ -123,17 +117,17 @@ if (config.build.productionGzip) {
|
|||
test: new RegExp(
|
||||
'\\.(' +
|
||||
config.build.productionGzipExtensions.join('|') +
|
||||
')$'
|
||||
')$',
|
||||
),
|
||||
threshold: 10240,
|
||||
minRatio: 0.8
|
||||
})
|
||||
)
|
||||
minRatio: 0.8,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (config.build.bundleAnalyzerReport) {
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
webpackConfig.plugins.push(new BundleAnalyzerPlugin());
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
||||
module.exports = webpackConfig;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const {merge} = require('webpack-merge')
|
||||
const prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ module.exports = {
|
|||
*/
|
||||
|
||||
// https://webpack.js.org/configuration/devtool/#development
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
// devtool: 'cheap-module-eval-source-map',
|
||||
devtool: 'eval-cheap-module-source-map',
|
||||
|
||||
// If you have problems debugging vue-files in devtools,
|
||||
// set this to false - it *may* help
|
||||
|
|
@ -57,7 +58,7 @@ module.exports = {
|
|||
|
||||
productionSourceMap: true,
|
||||
// https://webpack.js.org/configuration/devtool/#production
|
||||
devtool: '#source-map',
|
||||
devtool: 'source-map',
|
||||
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
|
|
|
|||
|
|
@ -24,19 +24,11 @@
|
|||
"slug": "geld-und-kauf",
|
||||
"__typename": "TopicNode"
|
||||
},
|
||||
"schoolClasses": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"schoolClasses": [{
|
||||
"id": "U2Nob29sQ2xhc3NOb2RlOjE=",
|
||||
"name": "FLID2018a",
|
||||
"__typename": "SchoolClassNode"
|
||||
},
|
||||
"__typename": "SchoolClassNodeEdge"
|
||||
}
|
||||
],
|
||||
"__typename": "SchoolClassNodeConnection"
|
||||
},
|
||||
}],
|
||||
"__typename": "UserNode",
|
||||
"onboardingVisited": true,
|
||||
"permissions": []
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export const SELECTED_CLASS_ID = 987;
|
||||
export const SELECTED_CLASS_ID_ENCODED = btoa(`SchoolClassNode:${SELECTED_CLASS_ID}`);
|
||||
const selectedClass = {
|
||||
id: btoa('SchoolClassNode:selectedClassId'),
|
||||
id: SELECTED_CLASS_ID_ENCODED,
|
||||
name: 'Moordale',
|
||||
readOnly: false,
|
||||
code: 'XXXX',
|
||||
|
|
@ -42,7 +44,7 @@ export default {
|
|||
id: getChapterId(),
|
||||
title: 'chapter-title',
|
||||
description: 'chapter-description',
|
||||
|
||||
bookmark: null
|
||||
}),
|
||||
ContentBlockNode: () => ({
|
||||
contents: [],
|
||||
|
|
@ -62,11 +64,7 @@ export default {
|
|||
readOnly: false,
|
||||
onboardingVisited: true,
|
||||
selectedClass,
|
||||
schoolClasses: {
|
||||
edges: [
|
||||
{node: selectedClass},
|
||||
],
|
||||
},
|
||||
schoolClasses: [selectedClass],
|
||||
recentModules: {
|
||||
edges: [],
|
||||
},
|
||||
|
|
@ -84,19 +82,18 @@ export default {
|
|||
}),
|
||||
ModuleNode: () => ({
|
||||
title: 'Module Title',
|
||||
slug: 'some slug',
|
||||
slug: 'some-slug',
|
||||
metaTitle: 'Meta Title',
|
||||
heroImage: '',
|
||||
teaser: '',
|
||||
intro: '',
|
||||
assignments: {nodes: []},
|
||||
assignments: [],
|
||||
objectiveGroups: [],
|
||||
id: getModuleId(),
|
||||
bookmark: null
|
||||
}),
|
||||
TopicNode: () => ({
|
||||
modules: {
|
||||
edges: [],
|
||||
},
|
||||
modules: [],
|
||||
}),
|
||||
RoomNode: () => ({
|
||||
title: 'A Room',
|
||||
|
|
@ -104,7 +101,7 @@ export default {
|
|||
appearance: 'blue',
|
||||
description: 'A Room description',
|
||||
schoolClass: {
|
||||
id: 'selectedClassId',
|
||||
id: SELECTED_CLASS_ID_ENCODED,
|
||||
},
|
||||
}),
|
||||
RoomEntryNode: () => ({
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ export default {
|
|||
heroImage: 'heroImage',
|
||||
teaser: 'A Module Mock Teaser',
|
||||
intro: 'intro',
|
||||
assignments: {},
|
||||
assignments: [],
|
||||
objectiveGroups: [],
|
||||
id: '',
|
||||
id: 'TW9kdWxlTm9kZToxMjM=',
|
||||
chapters: [],
|
||||
topic: {
|
||||
title: 'A Topic Mock Title',
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
describe('Bookmarks', () => {
|
||||
beforeEach(() => {
|
||||
// todo: mock all the graphql queries and mutations
|
||||
cy.exec('python ../server/manage.py prepare_bookmarks_for_cypress');
|
||||
|
||||
cy.viewport('macbook-15');
|
||||
cy.apolloLogin('rachel.green', 'test');
|
||||
});
|
||||
|
||||
it('should bookmark content block', () => {
|
||||
cy.visit('/module/lohn-und-budget/');
|
||||
|
||||
cy.get('.content-component').contains('Das folgende Interview').parent().parent().as('interviewContent');
|
||||
|
||||
cy.get('@interviewContent').within(() => {
|
||||
cy.get('.bookmark-actions__bookmark').click();
|
||||
cy.get('.bookmark-actions__add-note').click();
|
||||
});
|
||||
|
||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||
cy.get('.skillbox-input').type('Hallo Velo');
|
||||
});
|
||||
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
|
||||
cy.get('@interviewContent').within(() => {
|
||||
cy.get('.bookmark-actions__edit-note').click();
|
||||
});
|
||||
|
||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||
cy.get('.skillbox-input').clear().type('Hello Bike');
|
||||
});
|
||||
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
});
|
||||
});
|
||||
|
|
@ -40,7 +40,7 @@ describe('Email Verification', () => {
|
|||
|
||||
cy.visit('/license-activation');
|
||||
redeemCoupon('');
|
||||
cy.get('[data-cy="coupon-local-errors"]').contains('Coupon ist ein Pflichtfeld');
|
||||
cy.get('[data-cy="coupon-local-errors"]').contains('Coupon-Code ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('displays error if coupon input is wrong', () => {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ describe('Apply module visibility', () => {
|
|||
const {me: minimalMe} = getMinimalMe({});
|
||||
const me = {
|
||||
...minimalMe,
|
||||
schoolClasses: {
|
||||
edges: schoolClasses.map(scn => ({node: scn}))
|
||||
}
|
||||
schoolClasses
|
||||
};
|
||||
// name: '[\'FLID2018a\', \'Andere Klasse\']'
|
||||
const operations = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
import {getMinimalMe} from '../../support/helpers';
|
||||
import minimalModule from '../../fixtures/module.minimal';
|
||||
|
||||
const {me: minimalMe} = getMinimalMe({});
|
||||
|
||||
|
||||
describe('Bookmarks', () => {
|
||||
beforeEach(() => {
|
||||
cy.setup();
|
||||
cy.mockGraphqlOps({
|
||||
operations: {
|
||||
MeQuery: {
|
||||
me: minimalMe
|
||||
},
|
||||
ModuleDetailsQuery: {
|
||||
module: {
|
||||
...minimalModule,
|
||||
slug: 'my-module-slug',
|
||||
chapters: [
|
||||
{
|
||||
title: 'My super Chapter',
|
||||
contentBlocks: [
|
||||
{
|
||||
contents: [
|
||||
{
|
||||
type: 'text_block',
|
||||
value: {
|
||||
text: 'Das folgende Interview'
|
||||
},
|
||||
id: "df8212ee-3e82-49fa-977e-c4b60789163e"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
UpdateLastModule: {},
|
||||
UpdateContentBookmark: {
|
||||
updateContentBookmark: {
|
||||
success: true
|
||||
}
|
||||
},
|
||||
UpdateModuleBookmark: {
|
||||
updateModuleBookmark: {
|
||||
success: true
|
||||
}
|
||||
},
|
||||
UpdateChapterBookmark: {
|
||||
updateChapterBookmark: {
|
||||
success: true
|
||||
}
|
||||
},
|
||||
AddNote: ({input: {note}}) => ({
|
||||
addNote: {
|
||||
note
|
||||
}
|
||||
}),
|
||||
UpdateNote: ({input: {note}}) => ({
|
||||
updateNote: {
|
||||
note
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should bookmark instrument', () => {
|
||||
cy.visit();
|
||||
});
|
||||
|
||||
it('should bookmark module', () => {
|
||||
cy.visit('/module/lohn-und-budget/');
|
||||
cy.getByDataCy('module-bookmark-actions').as('moduleBookmark');
|
||||
|
||||
cy.get('@moduleBookmark').within(() => {
|
||||
cy.getByDataCy('bookmark-action').click();
|
||||
cy.getByDataCy('add-note-action').click();
|
||||
});
|
||||
|
||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||
cy.get('.skillbox-input').type('Hallo Velo');
|
||||
});
|
||||
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
|
||||
cy.get('@moduleBookmark').within(() => {
|
||||
cy.getByDataCy('edit-note-action').click();
|
||||
});
|
||||
|
||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||
cy.get('.skillbox-input').clear().type('Hello Bike');
|
||||
});
|
||||
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
});
|
||||
|
||||
it('should bookmark chapter', () => {
|
||||
cy.visit('/module/lohn-und-budget/');
|
||||
|
||||
cy.getByDataCy('chapter-bookmark-actions').as('chapterBookmark');
|
||||
|
||||
cy.get('@chapterBookmark').within(() => {
|
||||
cy.getByDataCy('bookmark-action').click();
|
||||
cy.getByDataCy('add-note-action').click();
|
||||
});
|
||||
|
||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||
cy.get('.skillbox-input').type('Hallo Velo');
|
||||
});
|
||||
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
|
||||
cy.get('@chapterBookmark').within(() => {
|
||||
cy.getByDataCy('edit-note-action').click();
|
||||
});
|
||||
|
||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||
cy.get('.skillbox-input').clear().type('Hello Bike');
|
||||
});
|
||||
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
});
|
||||
|
||||
it('should bookmark content block', () => {
|
||||
cy.visit('/module/lohn-und-budget/');
|
||||
|
||||
cy.getByDataCy('content-component').contains('Das folgende Interview').parent().parent().as('interviewContent');
|
||||
|
||||
cy.get('@interviewContent').within(() => {
|
||||
cy.get('.bookmark-actions__bookmark').click();
|
||||
cy.get('.bookmark-actions__add-note').click();
|
||||
});
|
||||
|
||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||
cy.get('.skillbox-input').type('Hallo Velo');
|
||||
});
|
||||
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
|
||||
cy.get('@interviewContent').within(() => {
|
||||
cy.get('.bookmark-actions__edit-note').click();
|
||||
});
|
||||
|
||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||
cy.get('.skillbox-input').clear().type('Hello Bike');
|
||||
});
|
||||
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
describe('Create Content Block', () => {
|
||||
it('visits the page', () => {
|
||||
// todo:
|
||||
// add mocks
|
||||
// cy.visit('/module/some-module/add/bliblablub');
|
||||
// add title
|
||||
// add text element
|
||||
// add list item
|
||||
// add text element to list item
|
||||
// add second list item
|
||||
// add text element to second list item
|
||||
// add another text element to second list item
|
||||
// save
|
||||
|
||||
// another test
|
||||
// go to pase
|
||||
// click cancel, go back
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// todo: another test
|
||||
// edit existing content block
|
||||
|
|
@ -28,7 +28,7 @@ describe('New student', () => {
|
|||
return {
|
||||
...me,
|
||||
onboardingVisited,
|
||||
schoolClasses: {edges: schoolClasses},
|
||||
schoolClasses,
|
||||
selectedClass: getSelectedClass(),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@ describe('Objective Visibility', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should display the correct objectives', () => {
|
||||
//todo: finish writing this test, this does nothing
|
||||
it.skip('should display the correct objectives', () => {
|
||||
cy.fakeLogin('rachel.green', 'test');
|
||||
|
||||
cy.visit('/module/lohn-und-budget');
|
||||
|
|
|
|||
|
|
@ -95,12 +95,10 @@ describe('Project Page', () => {
|
|||
beforeEach(() => {
|
||||
cy.setup();
|
||||
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphqlOps({
|
||||
operations,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('has the correct layout', () => {
|
||||
cy.visit('/portfolio/groot');
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ const getOperations = ({final, readOnly, classReadOnly = false}) => ({
|
|||
ModuleDetailsQuery: {
|
||||
module,
|
||||
},
|
||||
UpdateLastModule: {},
|
||||
AssignmentQuery: {
|
||||
assignment: {
|
||||
submission: {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const getOperations = ({readOnly, classReadOnly = false}) => ({
|
|||
...minimalModule,
|
||||
},
|
||||
},
|
||||
UpdateLastModule: {}
|
||||
});
|
||||
|
||||
const moduleNavigationTest = ({readOnly, classReadOnly = false, displayMenu}) => {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,12 @@ import {getMinimalMe} from '../../../support/helpers';
|
|||
const getOperations = ({readOnly}) => ({
|
||||
MeQuery: getMinimalMe({readOnly}),
|
||||
NewsTeasers: {
|
||||
newsTeasers: {
|
||||
edges: [
|
||||
newsTeasers: [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('Read Only News', () => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const getOperations = ({readOnly = false, classReadOnly = false}) => ({
|
|||
ProjectQuery: {
|
||||
project: {
|
||||
id: 'projectId',
|
||||
slug: 'project-name',
|
||||
final: false,
|
||||
student: {
|
||||
id: btoa('PrivateUserNode:1'),
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ describe('Room Team Management - Read only', () => {
|
|||
},
|
||||
},
|
||||
RoomsQuery: {
|
||||
rooms: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
rooms: [{
|
||||
id: '',
|
||||
slug: '',
|
||||
title: 'some room',
|
||||
|
|
@ -27,10 +24,7 @@ describe('Room Team Management - Read only', () => {
|
|||
id: SELECTED_CLASS_ID,
|
||||
name: 'bla',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ describe('Article page', () => {
|
|||
slug,
|
||||
id: 'room-entry-id',
|
||||
title: 'Some Room Entry, yay!',
|
||||
comments: {
|
||||
edges: [],
|
||||
},
|
||||
comments: [],
|
||||
};
|
||||
|
||||
const operations = {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@ describe('The Room Page', () => {
|
|||
cy.getByDataCy('room-actions').should('not.exist');
|
||||
});
|
||||
|
||||
it('changes visibility of a room', () => {
|
||||
// todo: re-enable once cypress can do it correctly
|
||||
it.skip('changes visibility of a room', () => {
|
||||
const MeQuery = getMinimalMe({
|
||||
isTeacher: true,
|
||||
});
|
||||
|
|
@ -155,9 +156,7 @@ describe('The Room Page', () => {
|
|||
MeQuery,
|
||||
RoomsQuery() {
|
||||
return {
|
||||
rooms: {
|
||||
edges: rooms.map(room => ({node: room})),
|
||||
},
|
||||
rooms
|
||||
};
|
||||
},
|
||||
RoomEntriesQuery: {
|
||||
|
|
@ -244,23 +243,18 @@ describe('The Room Page', () => {
|
|||
cy.getByDataCy('add-room-entry-modal').should('exist');
|
||||
});
|
||||
|
||||
it('changes class while on room page', () => {
|
||||
it.only('changes class while on room page', () => {
|
||||
const {me} = MeQuery;
|
||||
const otherClass = {
|
||||
id: btoa('SchoolClassNode:34'),
|
||||
name: 'Other Class',
|
||||
readOnly: false
|
||||
};
|
||||
const operations = {
|
||||
MeQuery: {
|
||||
me: {
|
||||
...me,
|
||||
schoolClasses: {
|
||||
edges: [
|
||||
...me.schoolClasses.edges,
|
||||
{
|
||||
node: {
|
||||
id: btoa('SchoolClassNode:other-class'),
|
||||
name: 'Other Class'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
schoolClasses: [...me.schoolClasses, otherClass],
|
||||
},
|
||||
},
|
||||
RoomEntriesQuery,
|
||||
|
|
@ -268,6 +262,15 @@ describe('The Room Page', () => {
|
|||
updateSettings: {
|
||||
success: true
|
||||
}
|
||||
},
|
||||
ModuleDetailsQuery: {
|
||||
me: {
|
||||
selectedClass: otherClass
|
||||
}
|
||||
},
|
||||
MySchoolClassQuery: {},
|
||||
RoomsQuery: {
|
||||
rooms: []
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -278,5 +281,6 @@ describe('The Room Page', () => {
|
|||
cy.getByDataCy('room-title').should('contain', 'A Room');
|
||||
cy.selectClass('Other Class');
|
||||
cy.url().should('include', 'rooms');
|
||||
cy.getByDataCy('selected-class-name').should('contain', 'Other Class');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
import {getMinimalMe} from '../../../support/helpers';
|
||||
import {SELECTED_CLASS_ID_ENCODED} from '../../../fixtures/mocks';
|
||||
|
||||
describe('The Rooms Page', () => {
|
||||
const getOperations = (isTeacher) => ({
|
||||
MeQuery: getMinimalMe({isTeacher}),
|
||||
RoomsQuery: {
|
||||
rooms: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
rooms: [{
|
||||
schoolClass: {
|
||||
id: btoa('SchoolClassNode:selectedClassId'),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
id: SELECTED_CLASS_ID_ENCODED,
|
||||
},
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -23,9 +18,7 @@ describe('The Rooms Page', () => {
|
|||
return {
|
||||
...operations,
|
||||
RoomsQuery: {
|
||||
rooms: {
|
||||
edges: [],
|
||||
},
|
||||
rooms: [],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -105,9 +98,7 @@ describe('The Rooms Page', () => {
|
|||
MeQuery,
|
||||
RoomsQuery() {
|
||||
return {
|
||||
rooms: {
|
||||
edges: rooms.map(room => ({node: room})),
|
||||
},
|
||||
rooms
|
||||
};
|
||||
},
|
||||
AddRoom({input: {room: {title, appearance, description}}}) {
|
||||
|
|
|
|||
|
|
@ -254,17 +254,13 @@ describe('Teacher Class Management', () => {
|
|||
let selectedClass = teacher.selectedClass;
|
||||
|
||||
const schoolClasses = [
|
||||
{
|
||||
node: teacher.selectedClass
|
||||
}
|
||||
teacher.selectedClass
|
||||
];
|
||||
|
||||
const me = () => ({
|
||||
...teacher,
|
||||
selectedClass,
|
||||
schoolClasses: {
|
||||
edges: schoolClasses
|
||||
}
|
||||
schoolClasses
|
||||
});
|
||||
|
||||
cy.mockGraphqlOps({
|
||||
|
|
@ -278,9 +274,7 @@ describe('Teacher Class Management', () => {
|
|||
name,
|
||||
readOnly: false
|
||||
};
|
||||
schoolClasses.push({
|
||||
node: schoolClass
|
||||
});
|
||||
schoolClasses.push(schoolClass);
|
||||
selectedClass = schoolClass;
|
||||
return {
|
||||
createSchoolClass: {
|
||||
|
|
|
|||
|
|
@ -11,12 +11,7 @@ describe('Sidebar', () => {
|
|||
MeQuery: {
|
||||
me: {
|
||||
...me,
|
||||
schoolClasses: {
|
||||
edges: [
|
||||
...me.schoolClasses.edges,
|
||||
{node: {}},
|
||||
],
|
||||
},
|
||||
schoolClasses: [...me.schoolClasses, {}],
|
||||
},
|
||||
},
|
||||
ProjectsQuery: {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ describe('Snapshot', () => {
|
|||
success: true,
|
||||
},
|
||||
},
|
||||
UpdateLastModule: {},
|
||||
ModuleSnapshotsQuery: {
|
||||
module: {
|
||||
...module,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
export const getMinimalMe = ({readOnly = false, classReadOnly = false, isTeacher = true} = {}) => {
|
||||
const selectedClass = {
|
||||
name: 'Selected Class',
|
||||
id: btoa('SchoolClassNode:selectedClassId'),
|
||||
id: btoa('SchoolClassNode:987'),
|
||||
readOnly: classReadOnly,
|
||||
};
|
||||
return {
|
||||
|
|
@ -12,11 +12,7 @@ export const getMinimalMe = ({readOnly = false, classReadOnly = false, isTeacher
|
|||
readOnly,
|
||||
isTeacher,
|
||||
selectedClass,
|
||||
schoolClasses: {
|
||||
edges: [
|
||||
{node: selectedClass},
|
||||
],
|
||||
},
|
||||
schoolClasses: [selectedClass],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -69,13 +65,7 @@ export const getMe = ({schoolClasses, teacher}) => {
|
|||
'slug': 'geld-und-kauf',
|
||||
'__typename': 'TopicNode',
|
||||
},
|
||||
'schoolClasses': {
|
||||
'edges': schoolClassNodes.map(scn => ({
|
||||
node: scn,
|
||||
'__typename': 'SchoolClassNodeEdge',
|
||||
})),
|
||||
'__typename': 'SchoolClassNodeConnection',
|
||||
},
|
||||
'schoolClasses': schoolClassNodes,
|
||||
'__typename': 'UserNode',
|
||||
'onboardingVisited': true,
|
||||
'permissions': teacher ? ['users.can_manage_school_class_content'] : [],
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,10 +5,11 @@
|
|||
"author": "ramon / chrigu",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
|
||||
"dev": "webpack serve --progress --config build/webpack.dev.conf.js",
|
||||
"analyze": "webpack --profile --json --config build/webpack.dev.conf.js > dist/stats.json && webpack-bundle-analyzer dist/stats.json",
|
||||
"start": ". ../server/.env && npm run dev",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"fix-lint": "eslint --ext .js,.vue --fix src",
|
||||
"lint": "eslint --ext .js,.vue,.ts src",
|
||||
"fix-lint": "eslint --ext .js,.vue,.ts --fix src",
|
||||
"build": "node build/build.js",
|
||||
"open:cypress:e2e": "npm run cypress:e2e:open",
|
||||
"open:cypress:frontend": "npm run cypress:frontend:open",
|
||||
|
|
@ -24,52 +25,48 @@
|
|||
"cypress:parallel:run": "cy2 run --parallel --record --config-file cypress.frontend.json --ci-build-id "
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.5.4",
|
||||
"@apollo/client": "^3.5.8",
|
||||
"@babel/core": "^7.16.7",
|
||||
"@babel/eslint-plugin": "^7.16.5",
|
||||
"@babel/plugin-transform-runtime": "^7.5.0",
|
||||
"@babel/polyfill": "^7.4.4",
|
||||
"@babel/preset-env": "^7.5.4",
|
||||
"@babel/preset-stage-2": "^7.0.0",
|
||||
"@babel/runtime": "^7.5.4",
|
||||
"@iam4x/cypress-graphql-mock": "0.0.1",
|
||||
"apollo-cache-inmemory": "^1.6.5",
|
||||
"apollo-client": "^2.6.8",
|
||||
"apollo-link": "^1.2.13",
|
||||
"apollo-link-error": "^1.1.12",
|
||||
"apollo-link-http": "^1.5.16",
|
||||
"@vue/composition-api": "^1.4.2",
|
||||
"appolo": "^6.0.19",
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-eslint": "^8.2.1",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"babel-plugin-transform-vue-jsx": "^3.5.0",
|
||||
"chalk": "^2.0.1",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"copy-webpack-plugin": "^10.1.0",
|
||||
"css-loader": "^0.28.0",
|
||||
"cy2": "^1.2.1",
|
||||
"dayjs": "^1.10.4",
|
||||
"dayjs": "^1.10.7",
|
||||
"debounce": "^1.2.0",
|
||||
"eslint": "^4.15.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-friendly-formatter": "^3.0.0",
|
||||
"eslint-loader": "^1.7.1",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.2.0",
|
||||
"eslint-plugin-promise": "^3.4.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eslint-plugin-vue": "^4.0.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"eslint-plugin-vue": "^8.3.0",
|
||||
"file-loader": "^1.1.4",
|
||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
"graphql": "^0.13.2",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"graphql": "^16.1.0",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"lodash": "^4.17.10",
|
||||
"moment": "^2.24.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"node-notifier": "^5.1.2",
|
||||
"node-sass": "^4.13.1",
|
||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||
"optimize-css-assets-webpack-plugin": "^6.0.1",
|
||||
"ora": "^1.2.0",
|
||||
"portfinder": "^1.0.13",
|
||||
"postcss-import": "^11.0.0",
|
||||
|
|
@ -79,31 +76,30 @@
|
|||
"sass-loader": "^7.1.0",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"survey-vue": "^1.8.77",
|
||||
"uglifyjs-webpack-plugin": "^1.1.1",
|
||||
"survey-vue": "^1.9.2",
|
||||
"unfetch": "^3.1.1",
|
||||
"uploadcare-widget": "^3.6.0",
|
||||
"url-loader": "^1.0.1",
|
||||
"uuid": "^3.2.1",
|
||||
"vee-validate": "^2.2.0",
|
||||
"vue": "^2.5.17",
|
||||
"vee-validate": "^3.4.14",
|
||||
"vue": "^2.6.14",
|
||||
"vue-analytics": "^5.16.2",
|
||||
"vue-apollo": "^3.0.0-beta.16",
|
||||
"vue-loader": "^15.9.6",
|
||||
"vue-apollo": "^3.1.0",
|
||||
"vue-loader": "^15.9.8",
|
||||
"vue-matomo": "^3.13.4-0",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-router": "^3.5.3",
|
||||
"vue-scrollto": "^2.11.0",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vue-toast-notification": "^0.4.1",
|
||||
"vue-vimeo-player": "0.0.6",
|
||||
"vuejs-logger": "1.5.5",
|
||||
"vuetify": "^1.1.8",
|
||||
"vuex": "^3.0.1",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack-bundle-analyzer": "^2.9.0",
|
||||
"webpack-dev-server": "^2.9.1",
|
||||
"webpack-merge": "^4.1.0",
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-dev-server": "^4.6.0",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -116,6 +112,8 @@
|
|||
"not ie <= 8"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.29",
|
||||
"babel-bridge": "^1.12.11",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
|
|
@ -128,9 +126,10 @@
|
|||
"jest-transform-graphql": "^2.1.0",
|
||||
"jest-transform-stub": "^2.0.0",
|
||||
"jest-watch-typeahead": "^0.3.1",
|
||||
"mock-apollo-client": "^0.7.0",
|
||||
"mock-apollo-client": "^1.2.0",
|
||||
"ts-loader": "^8.3.0",
|
||||
"typescript": "^4.4.3",
|
||||
"vue-jest": "^3.0.4"
|
||||
"typescript": "^4.5.4",
|
||||
"vue-jest": "^3.0.4",
|
||||
"webpack-cli": "^4.9.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,48 +2,52 @@
|
|||
<div
|
||||
:class="{'no-scroll': showModal || showMobileNavigation}"
|
||||
class="app"
|
||||
id="app">
|
||||
<read-only-banner/>
|
||||
<scroll-up/>
|
||||
id="app"
|
||||
>
|
||||
<read-only-banner />
|
||||
<scroll-up />
|
||||
<component
|
||||
:is="showModalDeprecated"
|
||||
v-if="showModalDeprecated"/>
|
||||
v-if="showModalDeprecated"
|
||||
/>
|
||||
<component
|
||||
:is="showModal"
|
||||
v-if="showModal"/>
|
||||
<component :is="layout"/>
|
||||
v-if="showModal"
|
||||
/>
|
||||
<component :is="layout" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DefaultLayout from '@/layouts/DefaultLayout';
|
||||
import SimpleLayout from '@/layouts/SimpleLayout';
|
||||
import FullScreenLayout from '@/layouts/FullScreenLayout';
|
||||
import PublicLayout from '@/layouts/PublicLayout';
|
||||
import BlankLayout from '@/layouts/BlankLayout';
|
||||
import SplitLayout from '@/layouts/SplitLayout';
|
||||
import Modal from '@/components/Modal';
|
||||
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
|
||||
import EditContentBlockWizard from '@/components/content-block-form/EditContentBlockWizard';
|
||||
import NewRoomEntryWizard from '@/components/rooms/room-entries/NewRoomEntryWizard';
|
||||
import EditRoomEntryWizard from '@/components/rooms/room-entries/EditRoomEntryWizard';
|
||||
import NewProjectEntryWizard from '@/components/portfolio/NewProjectEntryWizard';
|
||||
import EditProjectEntryWizard from '@/components/portfolio/EditProjectEntryWizard';
|
||||
import NewObjectiveWizard from '@/components/objective-groups/NewObjectiveWizard';
|
||||
import NewNoteWizard from '@/components/notes/NewNoteWizard';
|
||||
import EditNoteWizard from '@/components/notes/EditNoteWizard';
|
||||
import EditClassNameWizard from '@/components/school-class/EditClassNameWizard';
|
||||
import EditTeamNameWizard from '@/components/profile/EditTeamNameWizard';
|
||||
import FullscreenImage from '@/components/FullscreenImage';
|
||||
import FullscreenInfographic from '@/components/FullscreenInfographic';
|
||||
import FullscreenVideo from '@/components/FullscreenVideo';
|
||||
import DeactivatePerson from '@/components/profile/DeactivatePerson';
|
||||
import SnapshotCreated from '@/components/modules/SnapshotCreated';
|
||||
import ChangeVisibility from '@/components/rooms/ChangeVisibility';
|
||||
import {mapGetters} from 'vuex';
|
||||
import ScrollUp from '@/components/ScrollUp';
|
||||
import ReadOnlyBanner from '@/components/ReadOnlyBanner';
|
||||
|
||||
const Modal = () => import(/* webpackChunkName: "modals" */'@/components/Modal');
|
||||
const FullscreenImage = () => import(/* webpackChunkName: "modals" */'@/components/FullscreenImage');
|
||||
const FullscreenInfographic = () => import(/* webpackChunkName: "modals" */'@/components/FullscreenInfographic');
|
||||
const FullscreenVideo = () => import(/* webpackChunkName: "modals" */'@/components/FullscreenVideo');
|
||||
const DeactivatePerson = () => import(/* webpackChunkName: "modals" */'@/components/profile/DeactivatePerson');
|
||||
const SnapshotCreated = () => import(/* webpackChunkName: "modals" */'@/components/modules/SnapshotCreated');
|
||||
const ChangeVisibility = () => import(/* webpackChunkName: "modals" */'@/components/rooms/ChangeVisibility');
|
||||
const NewContentBlockWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/NewContentBlockWizard');
|
||||
const EditContentBlockWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/EditContentBlockWizard');
|
||||
const NewRoomEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/rooms/room-entries/NewRoomEntryWizard');
|
||||
const EditRoomEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/rooms/room-entries/EditRoomEntryWizard');
|
||||
const NewProjectEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/portfolio/NewProjectEntryWizard');
|
||||
const EditProjectEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/portfolio/EditProjectEntryWizard');
|
||||
const NewObjectiveWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/objective-groups/NewObjectiveWizard');
|
||||
const NewNoteWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/notes/NewNoteWizard');
|
||||
const EditNoteWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/notes/EditNoteWizard');
|
||||
const EditClassNameWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/school-class/EditClassNameWizard');
|
||||
const EditTeamNameWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/profile/EditTeamNameWizard');
|
||||
const DefaultLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/DefaultLayout');
|
||||
const SimpleLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/SimpleLayout');
|
||||
const FullScreenLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/FullScreenLayout');
|
||||
const PublicLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/PublicLayout');
|
||||
const BlankLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/BlankLayout');
|
||||
const SplitLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/SplitLayout');
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
||||
|
|
@ -93,6 +97,7 @@
|
|||
|
||||
<style lang="scss">
|
||||
@import "~styles/main.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
body {
|
||||
overflow-y: auto;
|
||||
|
|
|
|||
|
|
@ -2,14 +2,20 @@
|
|||
<div class="add-content">
|
||||
<a
|
||||
class="add-content__button"
|
||||
@click="addContent">
|
||||
<add-pointer class="add-content__icon"/>
|
||||
@click="addContent"
|
||||
>
|
||||
<add-pointer class="add-content__icon" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddPointer from '@/components/icons/AddPointer';
|
||||
import {
|
||||
CREATE_CONTENT_BLOCK_AFTER_PAGE,
|
||||
CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE,
|
||||
} from '@/router/module.names';
|
||||
|
||||
const AddPointer = () => import(/* webpackChunkName: "icons" */'@/components/icons/AddPointer');
|
||||
|
||||
export default {
|
||||
props: ['after', 'parent'],
|
||||
|
|
@ -18,15 +24,31 @@
|
|||
AddPointer
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
addContent() {
|
||||
if (this.parent && this.parent.__typename === 'ObjectiveGroupNode') {
|
||||
this.$store.dispatch('addObjective', this.parent.id);
|
||||
} else {
|
||||
this.$store.dispatch('addContentBlock', {
|
||||
after: this.after ? this.after.id : undefined,
|
||||
parent: this.parent ? this.parent.id : undefined
|
||||
});
|
||||
let route;
|
||||
const slug = this.$route.params.slug;
|
||||
if (this.after.id) {
|
||||
route = {
|
||||
name: CREATE_CONTENT_BLOCK_AFTER_PAGE,
|
||||
params: {
|
||||
after: this.after.id,
|
||||
slug
|
||||
}
|
||||
};
|
||||
} else {
|
||||
route = {
|
||||
name: CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE,
|
||||
params: {
|
||||
parent: this.parent.id
|
||||
}
|
||||
};
|
||||
}
|
||||
this.$router.push(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,8 +56,7 @@
|
|||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
.add-content {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
<template>
|
||||
<div
|
||||
class="add-content-element"
|
||||
@click="$emit('add-element', index)">
|
||||
<add-icon class="add-content-element__icon"/>
|
||||
@click="$emit('add-element', index)"
|
||||
>
|
||||
<add-icon class="add-content-element__icon" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddIcon from '@/components/icons/AddIcon';
|
||||
const AddIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/AddIcon');
|
||||
|
||||
export default {
|
||||
props: ['index'],
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
<template>
|
||||
<component
|
||||
:is="component"
|
||||
v-bind="properties"
|
||||
:class="{ 'add-widget--reverse': reverse }"
|
||||
class="add-widget"
|
||||
@click="$emit('click')">
|
||||
<add-icon class="add-widget__add"/>
|
||||
:is="component"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<add-icon class="add-widget__add" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddIcon from '@/components/icons/AddIcon.vue';
|
||||
const AddIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/AddIcon.vue');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -1,46 +1,64 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div class="assignment-with-submissions">
|
||||
<p class="assignment-with-submissions__text">{{ assignment.assignment }}</p>
|
||||
<p class="assignment-with-submissions__text">
|
||||
{{ assignment.assignment }}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<a
|
||||
class="button button--primary submissions-page__back"
|
||||
@click="$emit('back')">Aufgabe im Modul anzeigen</a>
|
||||
@click="$emit('back')"
|
||||
>Aufgabe im Modul anzeigen</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="assignment-with-submissions__solution"
|
||||
v-if="assignment.solution">
|
||||
<h4 class="assignment-with-submissions__heading">Lösung</h4>
|
||||
v-if="assignment.solution"
|
||||
>
|
||||
<h4 class="assignment-with-submissions__heading">
|
||||
Lösung
|
||||
</h4>
|
||||
<p
|
||||
class="assignment-with-submissions__solution-text"
|
||||
data-cy="assignment-solution"
|
||||
v-html="assignment.solution" />
|
||||
v-html="assignment.solution"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="assignment-with-submissions__no-submissions"
|
||||
v-if="!assignment.submissions.length">Zu diesem Auftrag sind noch keine Ergebnisse vorhanden.</p>
|
||||
v-if="!assignment.submissions.length"
|
||||
>
|
||||
Zu diesem Auftrag sind noch keine Ergebnisse vorhanden.
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="assignment-with-submissions__submissions submissions"
|
||||
v-if="assignment.submissions.length">
|
||||
v-if="assignment.submissions.length"
|
||||
>
|
||||
<div class="submissions__header student-submission-row submission-header">
|
||||
<p class="submission-header__title">Lernende</p>
|
||||
<p class="submission-header__title">Ergebnisse</p>
|
||||
<p class="submission-header__title">Feedback</p>
|
||||
<p class="submission-header__title">
|
||||
Lernende
|
||||
</p>
|
||||
<p class="submission-header__title">
|
||||
Ergebnisse
|
||||
</p>
|
||||
<p class="submission-header__title">
|
||||
Feedback
|
||||
</p>
|
||||
</div>
|
||||
<router-link
|
||||
:to="submissionLink(submission)"
|
||||
:key="submission.id"
|
||||
class="assignment-with-submissions__link"
|
||||
v-for="submission in submissions">
|
||||
v-for="submission in submissions"
|
||||
:key="submission.id"
|
||||
>
|
||||
<student-submission
|
||||
:submission="submission"
|
||||
class="assignment-with-submissions__submission"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -81,7 +99,7 @@
|
|||
if (this.currentFilter.id === '') {
|
||||
return true;
|
||||
}
|
||||
return submission.student.schoolClasses.edges.some(edge => edge.node.id === this.currentFilter.id);
|
||||
return submission.student.schoolClasses.some(schoolClass => schoolClass .id === this.currentFilter.id);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
<template>
|
||||
<router-link
|
||||
:to="to"
|
||||
class="sub-navigation-item back-link">
|
||||
<chevron-left class="back-link__icon sub-navigation-item__icon"/>
|
||||
class="sub-navigation-item back-link"
|
||||
>
|
||||
<chevron-left class="back-link__icon sub-navigation-item__icon" />
|
||||
{{ fullTitle }}
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChevronLeft from '@/components/icons/ChevronLeft';
|
||||
import {MODULE_PAGE} from '@/router/module.names';
|
||||
import {ROOMS_PAGE} from '@/router/room.names';
|
||||
import {PROJECTS_PAGE} from '@/router/portfolio.names';
|
||||
|
||||
const ChevronLeft = () => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronLeft');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
<template>
|
||||
<div
|
||||
:data-scrollto="chapter.id"
|
||||
class="chapter">
|
||||
class="chapter"
|
||||
data-cy="chapter"
|
||||
>
|
||||
<div
|
||||
:class="{'hideable-element--greyed-out': titleGreyedOut}"
|
||||
class="hideable-element"
|
||||
v-if="!titleHidden">
|
||||
v-if="!titleHidden"
|
||||
>
|
||||
<h3
|
||||
:id="'chapter-' + index"
|
||||
>{{ chapter.title }}</h3>
|
||||
>
|
||||
{{ chapter.title }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<visibility-action
|
||||
|
|
@ -21,6 +26,7 @@
|
|||
:bookmarked="chapter.bookmark"
|
||||
:note="note"
|
||||
class="chapter__bookmark-actions"
|
||||
data-cy="chapter-bookmark-actions"
|
||||
@add-note="addNote"
|
||||
@edit-note="editNote"
|
||||
@bookmark="bookmark(!chapter.bookmark)"
|
||||
|
|
@ -37,20 +43,23 @@
|
|||
v-if="editModule"
|
||||
/>
|
||||
<p
|
||||
class="chapter__description">
|
||||
class="chapter__description"
|
||||
>
|
||||
{{ chapter.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<add-content-button
|
||||
:parent="chapter"
|
||||
v-if="editModule"/>
|
||||
v-if="editModule"
|
||||
/>
|
||||
|
||||
<content-block
|
||||
:content-block="contentBlock"
|
||||
:parent="chapter.id"
|
||||
v-for="contentBlock in filteredContentBlocks"
|
||||
:key="contentBlock.id"
|
||||
v-for="contentBlock in filteredContentBlocks"/>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -100,6 +109,7 @@
|
|||
if (this.chapter && this.chapter.bookmark) {
|
||||
return this.chapter.bookmark.note;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
titleGreyedOut() {
|
||||
return this.textHidden(CHAPTER_TITLE_TYPE) && this.editModule;
|
||||
|
|
@ -134,26 +144,31 @@
|
|||
bookmarked,
|
||||
},
|
||||
},
|
||||
update: (store, response) => {
|
||||
update: (store) => {
|
||||
const query = CHAPTER_QUERY;
|
||||
const variables = {id};
|
||||
const data = store.readQuery({
|
||||
const {chapter} = store.readQuery({
|
||||
query,
|
||||
variables,
|
||||
});
|
||||
|
||||
const chapter = data.chapter;
|
||||
let bookmark;
|
||||
|
||||
if (bookmarked) {
|
||||
chapter.bookmark = {
|
||||
bookmark = {
|
||||
__typename: 'ChapterBookmarkNode',
|
||||
note: null,
|
||||
};
|
||||
} else {
|
||||
chapter.bookmark = null;
|
||||
bookmark = null;
|
||||
}
|
||||
|
||||
data.chapter = chapter;
|
||||
const data = {
|
||||
chapter: {
|
||||
...chapter,
|
||||
bookmark
|
||||
}
|
||||
};
|
||||
|
||||
store.writeQuery({
|
||||
data,
|
||||
|
|
@ -192,7 +207,7 @@
|
|||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_mixins.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
.chapter {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
<template>
|
||||
<div class="color-chooser">
|
||||
<div
|
||||
:key="index"
|
||||
:class="{'color-chooser__color-wrapper--selected': selectedColor === color.name}"
|
||||
class="color-chooser__color-wrapper"
|
||||
data-cy="color-select"
|
||||
v-for="(color, index) in colors"
|
||||
@click="$emit('input', color.name)">
|
||||
:key="index"
|
||||
@click="$emit('input', color.name)"
|
||||
>
|
||||
<div
|
||||
:class="'color-chooser__color--' + color.name"
|
||||
class="color-chooser__color">
|
||||
class="color-chooser__color"
|
||||
>
|
||||
<tick
|
||||
class="color-chooser__selected-icon"
|
||||
v-if="selectedColor === color.name"/>
|
||||
v-if="selectedColor === color.name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tick from '@/components/icons/Tick';
|
||||
const Tick = () => import(/* webpackChunkName: "icons" */'@/components/icons/Tick');
|
||||
|
||||
export default {
|
||||
props: ['selected-color'],
|
||||
props: ['selectedColor'],
|
||||
|
||||
components: {
|
||||
Tick
|
||||
|
|
|
|||
|
|
@ -1,63 +1,73 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{'hideable-element--greyed-out': hidden}"
|
||||
class="content-block__container hideable-element">
|
||||
class="content-block__container hideable-element content-list__parent"
|
||||
>
|
||||
<div
|
||||
:class="specialClass"
|
||||
class="content-block">
|
||||
class="content-block"
|
||||
>
|
||||
<div
|
||||
class="block-actions"
|
||||
v-if="canEditContentBlock && editModule">
|
||||
v-if="canEditContentBlock && editModule"
|
||||
>
|
||||
<user-widget
|
||||
v-bind="me"
|
||||
class="block-actions__user-widget content-block__user-widget"/>
|
||||
class="block-actions__user-widget content-block__user-widget"
|
||||
/>
|
||||
<more-options-widget>
|
||||
<li class="popover-links__link">
|
||||
<popover-link
|
||||
data-cy="delete-content-block-link"
|
||||
text="Löschen"
|
||||
@link-action="deleteContentBlock(contentBlock)" />
|
||||
|
||||
@link-action="deleteContentBlock(contentBlock)"
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li class="popover-links__link">
|
||||
<popover-link
|
||||
text="Bearbeiten"
|
||||
@link-action="editContentBlock(contentBlock)" />
|
||||
@link-action="editContentBlock(contentBlock)"
|
||||
/>
|
||||
</li>
|
||||
</more-options-widget>
|
||||
</div>
|
||||
<div class="content-block__visibility">
|
||||
<visibility-action
|
||||
:block="contentBlock"
|
||||
v-if="canEditModule"/>
|
||||
v-if="canEditModule"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3
|
||||
class="content-block__instrument-label"
|
||||
v-if="instrumentLabel !== ''">{{ instrumentLabel }}</h3>
|
||||
v-if="instrumentLabel !== ''"
|
||||
>
|
||||
{{ instrumentLabel }}
|
||||
</h3>
|
||||
<h4
|
||||
class="content-block__title"
|
||||
v-if="!contentBlock.indent">{{ contentBlock.title }}</h4>
|
||||
v-if="!contentBlock.indent"
|
||||
>
|
||||
{{ contentBlock.title }}
|
||||
</h4>
|
||||
|
||||
<content-component
|
||||
:key="component.id"
|
||||
:component="component"
|
||||
:root="root"
|
||||
:parent="contentBlock"
|
||||
:bookmarks="contentBlock.bookmarks"
|
||||
:notes="contentBlock.notes"
|
||||
v-for="component in contentBlocksWithContentLists.contents"
|
||||
:key="component.id"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<add-content-button
|
||||
:after="contentBlock"
|
||||
v-if="canEditModule"/>
|
||||
|
||||
v-if="canEditModule"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -65,7 +75,6 @@
|
|||
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
|
||||
import UserWidget from '@/components/UserWidget';
|
||||
import VisibilityAction from '@/components/visibility/VisibilityAction';
|
||||
import ContentComponent from '@/components/content-blocks/ContentComponent';
|
||||
|
||||
import CHAPTER_QUERY from '@/graphql/gql/queries/chapterQuery.gql';
|
||||
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';
|
||||
|
|
@ -76,6 +85,7 @@
|
|||
import {hidden} from '@/helpers/visibility';
|
||||
import {CONTENT_TYPE} from '@/consts/types';
|
||||
import PopoverLink from '@/components/ui/PopoverLink';
|
||||
const ContentComponent = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ContentComponent');
|
||||
|
||||
const instruments = {
|
||||
base_communication: 'Sprache & Kommunikation',
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
<modal
|
||||
:hide-header="true"
|
||||
:fullscreen="true"
|
||||
class="fullscreen-image">
|
||||
class="fullscreen-image"
|
||||
>
|
||||
<img
|
||||
:src="imageUrl"
|
||||
class="fullscreen-image__image">
|
||||
class="fullscreen-image__image"
|
||||
>
|
||||
</modal>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
<template>
|
||||
<modal :fullscreen="true">
|
||||
<component
|
||||
:value="value"
|
||||
:is="type"
|
||||
:value="value"/>
|
||||
/>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from '@/components/Modal';
|
||||
import InfogramBlock from '@/components/content-blocks/InfogramBlock';
|
||||
import GeniallyBlock from '@/components/content-blocks/GeniallyBlock';
|
||||
const InfogramBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/InfogramBlock');
|
||||
const GeniallyBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/GeniallyBlock');
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
<modal
|
||||
:hide-header="true"
|
||||
:fullscreen="true"
|
||||
class="fullscreen-video">
|
||||
class="fullscreen-video"
|
||||
>
|
||||
<iframe
|
||||
:src="src"
|
||||
width="2000"
|
||||
|
|
@ -11,9 +12,9 @@
|
|||
frameborder="0"
|
||||
webkitallowfullscreen
|
||||
mozallowfullscreen
|
||||
allowfullscreen/>
|
||||
allowfullscreen
|
||||
/>
|
||||
</modal>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -3,22 +3,26 @@
|
|||
<a
|
||||
class="header-bar__sidebar-link"
|
||||
data-cy="open-sidebar-link"
|
||||
@click.stop="openSidebar('navigation')">
|
||||
<hamburger class="header-bar__sidebar-icon"/>
|
||||
@click.stop="openSidebar('navigation')"
|
||||
>
|
||||
<hamburger class="header-bar__sidebar-icon" />
|
||||
</a>
|
||||
<content-navigation class="header-bar__content-navigation"/>
|
||||
<content-navigation class="header-bar__content-navigation" />
|
||||
<div class="user-header">
|
||||
<a
|
||||
class="user-header__sidebar-link" >
|
||||
class="user-header__sidebar-link"
|
||||
>
|
||||
<current-class
|
||||
class="user-header__current-class"
|
||||
@click.native.stop="openSidebar('profile')"/>
|
||||
@click.native.stop="openSidebar('profile')"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<user-widget
|
||||
v-bind="me"
|
||||
data-cy="header-user-widget"
|
||||
@click.native.stop="openSidebar('profile')"/>
|
||||
@click.native.stop="openSidebar('profile')"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
|
@ -26,20 +30,19 @@
|
|||
<script>
|
||||
import ContentNavigation from '@/components/book-navigation/ContentNavigation.vue';
|
||||
import UserWidget from '@/components/UserWidget.vue';
|
||||
import Logo from '@/components/icons/Logo';
|
||||
import CurrentClass from '@/components/school-class/CurrentClass';
|
||||
import Hamburger from '@/components/icons/Hamburger';
|
||||
|
||||
import openSidebar from '@/mixins/open-sidebar';
|
||||
import me from '@/mixins/me';
|
||||
|
||||
const Hamburger = () => import(/* webpackChunkName: "icons" */'@/components/icons/Hamburger');
|
||||
|
||||
export default {
|
||||
mixins: [openSidebar, me],
|
||||
|
||||
components: {
|
||||
ContentNavigation,
|
||||
UserWidget,
|
||||
Logo,
|
||||
CurrentClass,
|
||||
Hamburger,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="helpful-tooltip">
|
||||
<info-icon class="helpful-tooltip__icon"/>
|
||||
<info-icon class="helpful-tooltip__icon" />
|
||||
<div class="helpful-tooltip__tooltip">
|
||||
<div class="helpful-tooltip__text">
|
||||
{{ text }}
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import InfoIcon from '@/components/icons/InfoIcon';
|
||||
const InfoIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/InfoIcon');
|
||||
|
||||
export default {
|
||||
props: ['text'],
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
<template>
|
||||
<button
|
||||
:disabled="loading"
|
||||
class="loading-button button button--primary button--big">
|
||||
<template v-if="!loading">{{ label }}</template>
|
||||
:disabled="loading || disabled"
|
||||
class="loading-button button button--primary button--big"
|
||||
>
|
||||
<template v-if="!loading">
|
||||
{{ label }}
|
||||
</template>
|
||||
<loading-icon
|
||||
class="loading-button__icon"
|
||||
v-else/>
|
||||
v-else
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingIcon from '@/components/icons/LoadingIcon';
|
||||
const LoadingIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/LoadingIcon');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -18,6 +22,10 @@
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
|
|
@ -30,7 +38,7 @@
|
|||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_helpers.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
.loading-button {
|
||||
height: 52px;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
<a
|
||||
class="logout-widget__logout"
|
||||
data-cy="logout"
|
||||
@click="logout()">Abmelden</a>
|
||||
@click="logout()"
|
||||
>Abmelden</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,32 @@
|
|||
<template>
|
||||
<div class="mobile-header">
|
||||
<a @click="showMobileNavigation">
|
||||
<hamburger class="mobile-header__hamburger"/>
|
||||
<hamburger class="mobile-header__hamburger" />
|
||||
</a>
|
||||
|
||||
<router-link
|
||||
to="/"
|
||||
data-cy="mobile-home-link">
|
||||
<logo/>
|
||||
data-cy="mobile-home-link"
|
||||
>
|
||||
<logo />
|
||||
</router-link>
|
||||
|
||||
<user-widget
|
||||
v-bind="me"
|
||||
@click.native.stop="openSidebar('profile')"/>
|
||||
@click.native.stop="openSidebar('profile')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Logo from '@/components/icons/Logo';
|
||||
import Hamburger from '@/components/icons/Hamburger';
|
||||
import UserWidget from '@/components/UserWidget';
|
||||
|
||||
import me from '@/mixins/me';
|
||||
import openSidebar from '@/mixins/open-sidebar';
|
||||
|
||||
const Logo = () => import(/* webpackChunkName: "icons" */'@/components/icons/Logo');
|
||||
const Hamburger = () => import(/* webpackChunkName: "icons" */'@/components/icons/Hamburger');
|
||||
|
||||
export default {
|
||||
mixins: [me, openSidebar],
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@
|
|||
<div class="modal__backdrop">
|
||||
<div
|
||||
:class="{'modal--hide-header': hideHeader || fullscreen, 'modal--fullscreen': fullscreen, 'modal--small': small}"
|
||||
class="modal">
|
||||
class="modal"
|
||||
>
|
||||
<div class="modal__header">
|
||||
<slot name="header"/>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div class="modal__body">
|
||||
<slot/>
|
||||
<slot />
|
||||
<div
|
||||
class="modal__close-button"
|
||||
@click="hideModal">
|
||||
<cross class="modal__close-icon"/>
|
||||
@click="hideModal"
|
||||
>
|
||||
<cross class="modal__close-icon" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__footer">
|
||||
|
|
@ -19,7 +21,8 @@
|
|||
<!--<a class="button button--active">Speichern</a>-->
|
||||
<a
|
||||
class="button"
|
||||
@click="hideModal">Abbrechen</a>
|
||||
@click="hideModal"
|
||||
>Abbrechen</a>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -27,7 +30,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Cross from '@/components/icons/Cross';
|
||||
const Cross = () => import(/* webpackChunkName: "icons" */'@/components/icons/Cross');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@
|
|||
:class="{'skillbox-input--error': error}"
|
||||
:value="value"
|
||||
class="modal-input__inputfield skillbox-input"
|
||||
@input="$emit('input', $event.target.value)">
|
||||
@input="$emit('input', $event.target.value)"
|
||||
>
|
||||
<div
|
||||
class="modal-input__error"
|
||||
v-if="error">
|
||||
v-if="error"
|
||||
>
|
||||
Für Inhaltsblöcke muss zwingend ein Titel erfasst werden.
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,21 +3,24 @@
|
|||
<a
|
||||
class="more-options__more-link"
|
||||
data-cy="more-options-link"
|
||||
@click.stop="showMenu = !showMenu">
|
||||
<ellipses class="more-options__ellipses"/>
|
||||
@click.stop="showMenu = !showMenu"
|
||||
>
|
||||
<ellipses class="more-options__ellipses" />
|
||||
</a>
|
||||
<widget-popover
|
||||
class="more-options__popover"
|
||||
v-if="showMenu"
|
||||
@hide-me="showMenu = false">
|
||||
<slot/>
|
||||
@hide-me="showMenu = false"
|
||||
>
|
||||
<slot />
|
||||
</widget-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetPopover from '@/components/ui/WidgetPopover';
|
||||
import Ellipses from '@/components/icons/Ellipses.vue';
|
||||
|
||||
const Ellipses = () => import(/* webpackChunkName: "icons" */'@/components/icons/Ellipses.vue');
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
<div
|
||||
class="read-only-banner"
|
||||
data-cy="read-only-banner"
|
||||
v-if="me.readOnly || me.selectedClass.readOnly">
|
||||
v-if="me.readOnly || me.selectedClass.readOnly"
|
||||
>
|
||||
<div class="read-only-banner__content">
|
||||
<p class="read-only-banner__text">
|
||||
{{ readOnlyText }} Sie können Inhalte lesen, aber nicht
|
||||
|
|
@ -13,14 +14,16 @@
|
|||
:to="licenseActivationLink"
|
||||
data-cy="license-activation-link"
|
||||
class="read-only-banner__link button button--primary"
|
||||
v-if="me.readOnly">Neuen Lizenzcode eingeben
|
||||
v-if="me.readOnly"
|
||||
>
|
||||
Neuen Lizenzcode eingeben
|
||||
</router-link>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://myskillbox.ch/lesemodus"
|
||||
class="button button--secondary">Mehr Informationen zum Lesemodus</a>
|
||||
class="button button--secondary"
|
||||
>Mehr Informationen zum Lesemodus</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@
|
|||
<a
|
||||
class="scroll-up"
|
||||
v-if="scroll>200"
|
||||
@click="scrollTop">
|
||||
<arrow-up class="scroll-up__icon"/>
|
||||
@click="scrollTop"
|
||||
>
|
||||
<arrow-up class="scroll-up__icon" />
|
||||
</a>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArrowUp from '@/components/icons/ArrowUp';
|
||||
const ArrowUp = () => import(/* webpackChunkName: "icons" */'@/components/icons/ArrowUp');
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -3,16 +3,22 @@
|
|||
<div
|
||||
:class="{'section-block--navigatable': route}"
|
||||
class="section-block__illustration"
|
||||
@click="navigate()">
|
||||
<slot/>
|
||||
@click="navigate()"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div
|
||||
:class="{'section-block--navigatable': route}"
|
||||
class="section-block__title block-title"
|
||||
@click="navigate()">
|
||||
<h2 class="block-title__title">{{ title }}</h2>
|
||||
<h3 class="block-title__subtitle small-emph">{{ subtitle }}</h3>
|
||||
@click="navigate()"
|
||||
>
|
||||
<h2 class="block-title__title">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<h3 class="block-title__subtitle small-emph">
|
||||
{{ subtitle }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="section-block__content section-content">
|
||||
<div class="section-content__subsection subsection">
|
||||
|
|
@ -20,12 +26,14 @@
|
|||
:class="{'section-block--navigatable': route}"
|
||||
class="subsection__content button button--primary"
|
||||
v-if="route"
|
||||
@click="navigate()">
|
||||
@click="navigate()"
|
||||
>
|
||||
{{ linkText }}
|
||||
</a>
|
||||
<span
|
||||
class="subsection__content subsection__content--disabled"
|
||||
v-if="!route">Noch nicht verfügbar</span>
|
||||
v-if="!route"
|
||||
>Noch nicht verfügbar</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -33,7 +41,7 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['title', 'subtitle', 'route', 'link-text'],
|
||||
props: ['title', 'subtitle', 'route', 'linkText'],
|
||||
methods: {
|
||||
navigate() {
|
||||
if (this.route) {
|
||||
|
|
|
|||
|
|
@ -7,18 +7,24 @@
|
|||
<p>{{ submission.text | trimToLength(50) }}</p>
|
||||
<p
|
||||
class="entry__document"
|
||||
v-if="submission.document && submission.document.length > 0">
|
||||
v-if="submission.document && submission.document.length > 0"
|
||||
>
|
||||
<student-submission-document
|
||||
:document="submission.document"
|
||||
class="entry-document"/>
|
||||
class="entry-document"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="student-submission__feedback entry"
|
||||
v-if="submission.submissionFeedback">
|
||||
v-if="submission.submissionFeedback"
|
||||
>
|
||||
<p
|
||||
:class="{'entry__text--final': submission.submissionFeedback.final}"
|
||||
class="entry__text">{{ submission.submissionFeedback.text | trimToLength(50) }}</p>
|
||||
class="entry__text"
|
||||
>
|
||||
{{ submission.submissionFeedback.text | trimToLength(50) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,16 +2,17 @@
|
|||
<div class="submission-document">
|
||||
<p
|
||||
class="submission-document__content content"
|
||||
v-if="document && document.length > 0">
|
||||
<document-icon class="content__icon"/><span class="content__text">{{ filename }}</span>
|
||||
v-if="document && document.length > 0"
|
||||
>
|
||||
<document-icon class="content__icon" /><span class="content__text">{{ filename }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import DocumentIcon from '@/components/icons/DocumentIcon';
|
||||
import filenameFromUrl from '@/helpers/urls';
|
||||
const DocumentIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/DocumentIcon');
|
||||
|
||||
export default {
|
||||
name: 'StudentSubmissionDocument',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
<span class="user-widget__name">{{ firstName }} {{ lastName }}</span>
|
||||
<span
|
||||
class="user-widget__date"
|
||||
v-if="date">{{ date }}</span>
|
||||
v-if="date"
|
||||
>{{ date }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{'user-widget--is-profile': isProfile}"
|
||||
class="user-widget">
|
||||
class="user-widget"
|
||||
>
|
||||
<div
|
||||
class="user-widget__avatar"
|
||||
data-cy="user-widget-avatar">
|
||||
data-cy="user-widget-avatar"
|
||||
>
|
||||
<avatar
|
||||
:avatar-url="avatarUrl"
|
||||
:icon-highlighted="isProfile"
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
<router-link
|
||||
:to="{name: 'topic', params: {topicSlug: topic.slug}}"
|
||||
:class="{'book-topics__topic--active': topic.active, 'book-subnavigation__item--mobile': mobile}"
|
||||
:key="topic.id"
|
||||
tag="div"
|
||||
active-class="book-subnavigation__item--active"
|
||||
class="book-topics__topic book-subnavigation__item"
|
||||
v-for="topic in topics"
|
||||
@click.native="closeSidebar('navigation')">
|
||||
:key="topic.id"
|
||||
@click.native="closeSidebar('navigation')"
|
||||
>
|
||||
{{ topic.order }}.
|
||||
{{ topic.title }}
|
||||
</router-link>
|
||||
|
|
@ -44,7 +45,7 @@
|
|||
topics: {
|
||||
query: ALL_TOPICS_QUERY,
|
||||
manual: true,
|
||||
result({data, loading, networkStatus}) {
|
||||
result({data, loading}) {
|
||||
if (!loading) {
|
||||
this.topics = this.$getRidOfEdges(data).topics;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<nav
|
||||
:class="{'content-navigation--sidebar': isSidebar}"
|
||||
class="content-navigation">
|
||||
class="content-navigation"
|
||||
>
|
||||
<div class="content-navigation__primary">
|
||||
<div class="content-navigation__item">
|
||||
<router-link
|
||||
|
|
@ -9,13 +10,14 @@
|
|||
:to="topicRoute"
|
||||
active-class="content-navigation__link--active"
|
||||
class="content-navigation__link"
|
||||
@click.native="close">Themen
|
||||
@click.native="close"
|
||||
>
|
||||
Themen
|
||||
</router-link>
|
||||
|
||||
<book-topic-navigation
|
||||
v-if="isSidebar"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content-navigation__item">
|
||||
|
|
@ -23,7 +25,9 @@
|
|||
to="/instruments"
|
||||
active-class="content-navigation__link--active"
|
||||
class="content-navigation__link"
|
||||
@click.native="close">Instrumente
|
||||
@click.native="close"
|
||||
>
|
||||
Instrumente
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
|
|
@ -34,7 +38,9 @@
|
|||
class="content-navigation__link"
|
||||
data-cy="news-navigation-link"
|
||||
v-if="!me.readOnly"
|
||||
@click.native="close">News
|
||||
@click.native="close"
|
||||
>
|
||||
News
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -45,7 +51,7 @@
|
|||
data-cy="home-link"
|
||||
v-if="!isSidebar"
|
||||
>
|
||||
<logo class="content-navigation__logo-icon"/>
|
||||
<logo class="content-navigation__logo-icon" />
|
||||
</router-link>
|
||||
|
||||
<div class="content-navigation__secondary">
|
||||
|
|
@ -55,28 +61,35 @@
|
|||
to="/rooms"
|
||||
active-class="content-navigation__link--active"
|
||||
class="content-navigation__link content-navigation__link--secondary"
|
||||
@click.native="close">Räume
|
||||
@click.native="close"
|
||||
>
|
||||
Räume
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="content-navigation__item content-navigation__item--secondary"
|
||||
v-if="showPortfolio">
|
||||
v-if="showPortfolio"
|
||||
>
|
||||
<router-link
|
||||
to="/portfolio"
|
||||
active-class="content-navigation__link--active"
|
||||
class="content-navigation__link content-navigation__link--secondary"
|
||||
@click.native="close">Portfolio
|
||||
@click.native="close"
|
||||
>
|
||||
Portfolio
|
||||
</router-link>
|
||||
</div>
|
||||
<div
|
||||
class="content-navigation__item content-navigation__item--secondary"
|
||||
v-if="isSidebar">
|
||||
v-if="isSidebar"
|
||||
>
|
||||
<a
|
||||
href="https://myskillbox.ch/support"
|
||||
target="_blank"
|
||||
class="content-navigation__link content-navigation__link--secondary"
|
||||
@click="close">Support
|
||||
@click="close"
|
||||
>Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -84,12 +97,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Logo from '@/components/icons/Logo';
|
||||
import BookTopicNavigation from '@/components/book-navigation/BookTopicNavigation';
|
||||
|
||||
import sidebarMixin from '@/mixins/sidebar';
|
||||
import meMixin from '@/mixins/me';
|
||||
|
||||
const Logo = () => import(/* webpackChunkName: "icons" */'@/components/icons/Logo');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
isSidebar: {
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
<template>
|
||||
<transition name="slide">
|
||||
<div
|
||||
v-click-outside="close"
|
||||
class="navigation-sidebar"
|
||||
v-if="sidebar.navigation"
|
||||
v-click-outside="close"
|
||||
>
|
||||
<content-navigation
|
||||
:is-sidebar="true"
|
||||
class="navigation-sidebar__main"/>
|
||||
class="navigation-sidebar__main"
|
||||
/>
|
||||
<div
|
||||
class="navigation-sidebar__close-button"
|
||||
@click="close">
|
||||
<cross class="navigation-sidebar__close-icon"/>
|
||||
@click="close"
|
||||
>
|
||||
<cross class="navigation-sidebar__close-icon" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Cross from '@/components/icons/Cross';
|
||||
import ContentNavigation from '@/components/book-navigation/ContentNavigation';
|
||||
|
||||
import sidebarMixin from '@/mixins/sidebar';
|
||||
|
||||
import {meQuery} from '@/graphql/queries';
|
||||
const Cross = () => import(/* webpackChunkName: "icons" */'@/components/icons/Cross');
|
||||
|
||||
export default {
|
||||
mixins: [sidebarMixin],
|
||||
|
|
@ -33,21 +34,12 @@
|
|||
Cross
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
me: {}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.closeSidebar('navigation');
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
me: meQuery
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,29 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{ 'sub-navigation-item--active': show}"
|
||||
class="sub-navigation-item"
|
||||
v-click-outside="close"
|
||||
class="sub-navigation-item">
|
||||
>
|
||||
<div
|
||||
class="sub-navigation-item__title"
|
||||
@click="show = !show">
|
||||
@click="show = !show"
|
||||
>
|
||||
{{ title }}
|
||||
<chevron-down class="sub-navigation-item__icon sub-navigation-item__chevron-down"/>
|
||||
<chevron-up class="sub-navigation-item__icon sub-navigation-item__chevron-up"/>
|
||||
<chevron-down class="sub-navigation-item__icon sub-navigation-item__chevron-down" />
|
||||
<chevron-up class="sub-navigation-item__icon sub-navigation-item__chevron-up" />
|
||||
</div>
|
||||
<div
|
||||
class="sub-navigation-item__nav-items book-subnavigation"
|
||||
v-if="show">
|
||||
<slot/>
|
||||
v-if="show"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChevronDown from '@/components/icons/ChevronDown';
|
||||
import ChevronUp from '@/components/icons/ChevronUp';
|
||||
const ChevronDown = () => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronDown');
|
||||
const ChevronUp = () => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronUp');
|
||||
|
||||
export default {
|
||||
props: ['title'],
|
||||
|
|
@ -37,7 +40,7 @@
|
|||
},
|
||||
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
$route() {
|
||||
this.show = false;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<a
|
||||
class="add-content-link"
|
||||
@click="$emit('click')"
|
||||
><plus-icon class="add-content-link__icon" /> <span class="add-content-link__text">Neuer Inhalt</span></a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PlusIcon from '@/components/icons/PlusIcon';
|
||||
export default {
|
||||
components: { PlusIcon }
|
||||
|
||||
//
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
$color: $color-silver-dark;
|
||||
|
||||
.add-content-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: $small-spacing;
|
||||
fill: $color;
|
||||
}
|
||||
&__text {
|
||||
// custom style, because the view needs this
|
||||
@include link-base;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
<template>
|
||||
<div class="content-element">
|
||||
<content-block-element-chooser-widget
|
||||
:class="['content-element__component', 'content-element__chooser']"
|
||||
v-bind="element"
|
||||
v-if="isChooser"
|
||||
|
||||
@change-type="changeType"
|
||||
/>
|
||||
<content-form-section
|
||||
:title="title"
|
||||
:icon="icon"
|
||||
v-else
|
||||
>
|
||||
<div class="content-element__section">
|
||||
<component
|
||||
:class="['content-element__component']"
|
||||
v-bind="element"
|
||||
:is="component"
|
||||
|
||||
@change-text="changeText"
|
||||
|
||||
@link-change-url="changeUrl"
|
||||
@change-url="changeUrl"
|
||||
|
||||
@switch-to-document="switchToDocument"
|
||||
|
||||
@assignment-change-title="changeAssignmentTitle"
|
||||
@assignment-change-assignment="changeAssignmentAssignment"
|
||||
/>
|
||||
|
||||
<a
|
||||
class="contents-form__remove icon-button"
|
||||
@click="$emit('remove')"
|
||||
>
|
||||
<trash-icon
|
||||
class="contents-form__trash-icon icon-button__icon"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</content-form-section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContentFormSection from '@/components/content-block-form/ContentFormSection';
|
||||
const TrashIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/TrashIcon');
|
||||
const ContentBlockElementChooserWidget = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/ContentBlockElementChooserWidget');
|
||||
const LinkForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/LinkForm');
|
||||
const VideoForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/VideoForm');
|
||||
const ImageForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/ImageForm');
|
||||
const DocumentForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/DocumentForm');
|
||||
const AssignmentForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/AssignmentForm');
|
||||
const TextForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/TextForm');
|
||||
|
||||
const CHOOSER = 'content-block-element-chooser-widget';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
ContentFormSection,
|
||||
TrashIcon,
|
||||
ContentBlockElementChooserWidget,
|
||||
LinkForm,
|
||||
VideoForm,
|
||||
ImageForm,
|
||||
DocumentForm,
|
||||
AssignmentForm,
|
||||
TextForm,
|
||||
},
|
||||
|
||||
computed: {
|
||||
isChooser() {
|
||||
return this.component === CHOOSER;
|
||||
},
|
||||
type() {
|
||||
return this.getType(this.element);
|
||||
},
|
||||
component() {
|
||||
return this.type.component;
|
||||
},
|
||||
title() {
|
||||
return this.type.title;
|
||||
},
|
||||
icon() {
|
||||
return this.type.icon;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getType(element) {
|
||||
switch (element.type) {
|
||||
case 'link_block':
|
||||
return {
|
||||
component: 'link-form',
|
||||
title: 'Link',
|
||||
icon: 'link-icon'
|
||||
};
|
||||
case 'video_block':
|
||||
return {
|
||||
component: 'video-form',
|
||||
title: 'Video',
|
||||
icon: 'video-icon'
|
||||
};
|
||||
case 'image_url_block':
|
||||
return {
|
||||
component: 'image-form',
|
||||
title: 'Bild',
|
||||
icon: 'image-icon'
|
||||
};
|
||||
case 'text_block':
|
||||
return {
|
||||
component: 'text-form',
|
||||
title: 'Text',
|
||||
icon: 'text-icon'
|
||||
};
|
||||
case 'assignment':
|
||||
return {
|
||||
component: 'assignment-form',
|
||||
title: 'Aufgabe & Ergebnis',
|
||||
icon: 'speech-bubble-icon'
|
||||
};
|
||||
case 'document_block':
|
||||
return {
|
||||
component: 'document-form',
|
||||
title: 'Dokument',
|
||||
icon: 'document-icon'
|
||||
};
|
||||
}
|
||||
return {
|
||||
component: CHOOSER,
|
||||
title: '',
|
||||
icon: ''
|
||||
};
|
||||
},
|
||||
_updateProperty(value, key) {
|
||||
// const content = this.localContentBlock.contents[index];
|
||||
const content = this.element;
|
||||
this.update({
|
||||
...content,
|
||||
value: {
|
||||
...content.value,
|
||||
[key]: value,
|
||||
},
|
||||
});
|
||||
},
|
||||
changeUrl(value) {
|
||||
this._updateProperty(value, 'url');
|
||||
},
|
||||
changeText(value) {
|
||||
this._updateProperty(value, 'text');
|
||||
},
|
||||
changeAssignmentTitle(value) {
|
||||
this._updateProperty(value, 'title');
|
||||
},
|
||||
changeAssignmentAssignment(value) {
|
||||
this._updateProperty(value, 'assignment');
|
||||
},
|
||||
changeType({type, convertToList}, value) {
|
||||
let el = {
|
||||
type: type,
|
||||
value: Object.assign({}, value),
|
||||
};
|
||||
switch (type) {
|
||||
case 'text_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
text: '',
|
||||
},
|
||||
};
|
||||
break;
|
||||
case 'link_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
text: '',
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
break;
|
||||
case 'video_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
break;
|
||||
case 'document_block':
|
||||
el = {
|
||||
...el,
|
||||
value: Object.assign({
|
||||
url: '',
|
||||
}, value),
|
||||
};
|
||||
break;
|
||||
case 'image_url_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (convertToList) {
|
||||
el = {
|
||||
type: 'content_list_item',
|
||||
contents: [el]
|
||||
};
|
||||
}
|
||||
this.update(el);
|
||||
},
|
||||
update(element) {
|
||||
this.$emit('update', element);
|
||||
},
|
||||
switchToDocument(value) {
|
||||
this.changeType('document_block', value);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
.content-element {
|
||||
&__section {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 50px;
|
||||
grid-auto-rows: auto;
|
||||
/*width: 95%; // reserve space for scrollbar*/
|
||||
}
|
||||
|
||||
&__chooser {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<div class="content-form-section">
|
||||
<h2 class="content-form-section__heading">
|
||||
<component
|
||||
class="content-form-section__icon"
|
||||
:is="icon"
|
||||
/> <span class="content-form-section__title">{{ title }}</span>
|
||||
</h2>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import formElementIcons from '@/components/ui/form-element-icons';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
components: {
|
||||
...formElementIcons
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
.content-form-section {
|
||||
@include default-box-shadow;
|
||||
border-radius: $default-border-radius;
|
||||
padding: $small-spacing $medium-spacing;
|
||||
margin-bottom: $medium-spacing;
|
||||
|
||||
&__heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__title {
|
||||
@include heading-4;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: $small-spacing;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<modal>
|
||||
<template slot="header">
|
||||
<template #header>
|
||||
<modal-input
|
||||
:placeholder="titlePlaceholder"
|
||||
:value="localContentBlock.title"
|
||||
|
|
@ -24,40 +24,15 @@
|
|||
@add-element="addElement"
|
||||
/>
|
||||
<div
|
||||
:key="index"
|
||||
class="contents-form__element"
|
||||
v-for="(element, index) in localContentBlock.contents">
|
||||
<component
|
||||
:is="type(element)"
|
||||
:class="{'contents-form__chooser': type(element) === 'content-block-element-chooser-widget'}"
|
||||
v-bind="element"
|
||||
:index="index"
|
||||
class="contents-form__element-component"
|
||||
@change-type="changeType"
|
||||
|
||||
@link-change-url="changeLinkUrl"
|
||||
@link-change-text="changeLinkText"
|
||||
|
||||
@text-change-value="changeTextValue"
|
||||
|
||||
@document-change-url="changeDocumentUrl"
|
||||
|
||||
@image-change-url="changeImageUrl"
|
||||
|
||||
@video-change-url="changeVideoUrl"
|
||||
|
||||
@switch-to-document="switchToDocument"
|
||||
|
||||
@assignment-change-title="changeAssignmentTitle"
|
||||
@assignment-change-assignment="changeAssignmentAssignment"
|
||||
v-for="(element, index) in localContentBlock.contents"
|
||||
:key="index"
|
||||
>
|
||||
<content-element
|
||||
:element="element"
|
||||
@update="update(index, $event)"
|
||||
@remove="remove(index)"
|
||||
/>
|
||||
<a
|
||||
class="contents-form__remove icon-button"
|
||||
@click="removeElement(index)">
|
||||
<trash-icon
|
||||
class="contents-form__trash-icon icon-button__icon"
|
||||
v-if="type(element) !== 'content-block-element-chooser-widget'"/>
|
||||
</a>
|
||||
|
||||
<add-content-element
|
||||
:index="index"
|
||||
|
|
@ -66,65 +41,56 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div slot="footer">
|
||||
<template #footer>
|
||||
<div>
|
||||
<a
|
||||
:class="{'button--disabled': disableSave}"
|
||||
class="button button--primary"
|
||||
data-cy="modal-save-button"
|
||||
@click="save">Speichern</a>
|
||||
@click="save"
|
||||
>Speichern</a>
|
||||
<a
|
||||
class="button"
|
||||
@click="$emit('hide')">Abbrechen</a>
|
||||
@click="$emit('hide')"
|
||||
>Abbrechen</a>
|
||||
</div>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from '@/components/Modal';
|
||||
import ContentBlockElementChooserWidget from '@/components/content-forms/ContentBlockElementChooserWidget';
|
||||
import ModalInput from '@/components/ModalInput';
|
||||
import AddContentElement from '@/components/AddContentElement';
|
||||
import LinkForm from '@/components/content-forms/LinkForm';
|
||||
import VideoForm from '@/components/content-forms/VideoForm';
|
||||
import ImageForm from '@/components/content-forms/ImageForm';
|
||||
import DocumentForm from '@/components/content-forms/DocumentForm';
|
||||
import AssignmentForm from '@/components/content-forms/AssignmentForm';
|
||||
import TextForm from '@/components/content-forms/TextForm';
|
||||
import TrashIcon from '@/components/icons/TrashIcon';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
|
||||
import {meQuery} from '@/graphql/queries';
|
||||
|
||||
const ModalInput = () => import(/* webpackChunkName: "content-forms" */'@/components/ModalInput');
|
||||
const AddContentElement = () => import(/* webpackChunkName: "content-forms" */'@/components/AddContentElement');
|
||||
const ContentElement = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/ContentElement');
|
||||
|
||||
const Modal = () => import('@/components/Modal');
|
||||
const Checkbox = () => import('@/components/ui/Checkbox');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
'content-block': Object,
|
||||
'block-type': {
|
||||
contentBlock: Object,
|
||||
blockType: {
|
||||
type: String,
|
||||
default: 'ContentBlock'
|
||||
default: 'ContentBlock',
|
||||
},
|
||||
'show-task-selection': {
|
||||
showTaskSelection: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
'disable-save': {
|
||||
disableSave: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
ContentElement,
|
||||
Modal,
|
||||
ContentBlockElementChooserWidget,
|
||||
ModalInput,
|
||||
AddContentElement,
|
||||
LinkForm,
|
||||
VideoForm,
|
||||
ImageForm,
|
||||
DocumentForm,
|
||||
AssignmentForm,
|
||||
TextForm,
|
||||
TrashIcon,
|
||||
Checkbox
|
||||
Checkbox,
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -134,14 +100,14 @@
|
|||
title: this.contentBlock.title,
|
||||
contents: [...this.contentBlock.contents],
|
||||
id: this.contentBlock.id || undefined,
|
||||
isAssignment: this.contentBlock.type && this.contentBlock.type.toLowerCase() === 'task'
|
||||
isAssignment: this.contentBlock.type && this.contentBlock.type.toLowerCase() === 'task',
|
||||
}),
|
||||
me: {}
|
||||
me: {},
|
||||
};
|
||||
},
|
||||
|
||||
apollo: {
|
||||
me: meQuery
|
||||
me: meQuery,
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
@ -150,123 +116,15 @@
|
|||
},
|
||||
taskSelection() {
|
||||
return this.showTaskSelection && this.me.permissions.includes('users.can_manage_school_class_content');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
type(element) {
|
||||
switch (element.type) {
|
||||
case 'link_block':
|
||||
return 'link-form';
|
||||
case 'video_block':
|
||||
return 'video-form';
|
||||
case 'image_url_block':
|
||||
return 'image-form';
|
||||
case 'text_block':
|
||||
return 'text-form';
|
||||
case 'assignment':
|
||||
return 'assignment-form';
|
||||
case 'document_block':
|
||||
return 'document-form';
|
||||
}
|
||||
return 'content-block-element-chooser-widget';
|
||||
setContentBlockType(checked) {
|
||||
this.localContentBlock.isAssignment = checked;
|
||||
},
|
||||
_updateProperty(value, index, key) {
|
||||
const content = this.localContentBlock.contents[index];
|
||||
this.localContentBlock.contents.splice(index, 1, {
|
||||
...content,
|
||||
value: {
|
||||
...content.value,
|
||||
[key]: value
|
||||
}
|
||||
});
|
||||
},
|
||||
changeLinkUrl(value, index) {
|
||||
this._updateProperty(value, index, 'url');
|
||||
},
|
||||
changeLinkText(value, index) {
|
||||
this._updateProperty(value, index, 'text');
|
||||
},
|
||||
changeVideoUrl(value, index) {
|
||||
this._updateProperty(value, index, 'url');
|
||||
},
|
||||
changeImageUrl(value, index) {
|
||||
this._updateProperty(value, index, 'url');
|
||||
},
|
||||
changeDocumentUrl(value, index) {
|
||||
this._updateProperty(value, index, 'url');
|
||||
},
|
||||
changeTextValue(value, index) {
|
||||
this._updateProperty(value, index, 'text');
|
||||
},
|
||||
changeAssignmentTitle(value, index) {
|
||||
this._updateProperty(value, index, 'title');
|
||||
},
|
||||
changeAssignmentAssignment(value, index) {
|
||||
this._updateProperty(value, index, 'assignment');
|
||||
},
|
||||
removeElement(index) {
|
||||
this.localContentBlock.contents.splice(index, 1);
|
||||
},
|
||||
addElement(index) {
|
||||
this.localContentBlock.contents.splice(index + 1, 0, {
|
||||
hideAssignment: this.blockType !== 'ContentBlock'
|
||||
});
|
||||
},
|
||||
updateTitle(title) {
|
||||
this.localContentBlock.title = title;
|
||||
this.error = false;
|
||||
},
|
||||
changeType(index, type, value) {
|
||||
let el = {
|
||||
type: type,
|
||||
value: Object.assign({}, value)
|
||||
};
|
||||
switch (type) {
|
||||
case 'text_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
text: ''
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'link_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
text: '',
|
||||
url: ''
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'video_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
url: ''
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'document_block':
|
||||
el = {
|
||||
...el,
|
||||
value: Object.assign({
|
||||
url: ''
|
||||
}, value)
|
||||
};
|
||||
break;
|
||||
case 'image_url_block':
|
||||
el = {
|
||||
...el,
|
||||
value: {
|
||||
url: ''
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
this.localContentBlock.contents.splice(index, 1, el);
|
||||
update(index, element) {
|
||||
this.localContentBlock.contents.splice(index, 1, element);
|
||||
},
|
||||
save() {
|
||||
if (!this.disableSave) {
|
||||
|
|
@ -277,27 +135,31 @@
|
|||
this.$emit('save', this.localContentBlock);
|
||||
}
|
||||
},
|
||||
setContentBlockType(checked, localContentBlock) {
|
||||
this.localContentBlock.isAssignment = checked;
|
||||
updateTitle(title) {
|
||||
this.localContentBlock.title = title;
|
||||
this.error = false;
|
||||
},
|
||||
addElement(index) {
|
||||
this.localContentBlock.contents.splice(index + 1, 0, {
|
||||
hideAssignment: this.blockType !== 'ContentBlock',
|
||||
});
|
||||
},
|
||||
remove(index) {
|
||||
this.localContentBlock.contents.splice(index, 1);
|
||||
},
|
||||
|
||||
},
|
||||
switchToDocument(index, value) {
|
||||
this.changeType(index, 'document_block', value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
.contents-form {
|
||||
/* top level does not exist, because of the modal */
|
||||
|
||||
&__element {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 50px;
|
||||
grid-auto-rows: auto;
|
||||
/*width: 95%; // reserve space for scrollbar*/
|
||||
|
||||
}
|
||||
|
||||
&__element-component {
|
||||
|
|
@ -310,10 +172,6 @@
|
|||
&__trash-icon {
|
||||
}
|
||||
|
||||
&__chooser {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
&__add {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<div
|
||||
:class="componentClass"
|
||||
:data-scrollto="component.id"
|
||||
data-cy="content-component"
|
||||
>
|
||||
<bookmark-actions
|
||||
:bookmarked="bookmarked"
|
||||
|
|
@ -9,11 +10,12 @@
|
|||
v-if="showBookmarkActions"
|
||||
@add-note="addNote(component.id)"
|
||||
@edit-note="editNote"
|
||||
@bookmark="bookmarkContent(component.id, !bookmarked)"/>
|
||||
@bookmark="bookmarkContent(component.id, !bookmarked)"
|
||||
/>
|
||||
<component
|
||||
:is="component.type"
|
||||
v-bind="component"
|
||||
:parent="parent"
|
||||
:is="component.type"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -21,28 +23,28 @@
|
|||
<script>
|
||||
import {mapState} from 'vuex';
|
||||
|
||||
import TextBlock from '@/components/content-blocks/TextBlock';
|
||||
import InstrumentWidget from '@/components/content-blocks/InstrumentWidget';
|
||||
import ImageBlock from '@/components/content-blocks/ImageBlock';
|
||||
import ImageUrlBlock from '@/components/content-blocks/ImageUrlBlock';
|
||||
import VideoBlock from '@/components/content-blocks/VideoBlock';
|
||||
import LinkBlock from '@/components/content-blocks/LinkBlock';
|
||||
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
|
||||
import InfogramBlock from '@/components/content-blocks/InfogramBlock';
|
||||
import ThinglinkBlock from '@/components/content-blocks/ThinglinkBlock';
|
||||
import GeniallyBlock from '@/components/content-blocks/GeniallyBlock';
|
||||
import SubtitleBlock from '@/components/content-blocks/SubtitleBlock';
|
||||
import SectionTitleBlock from '@/components/content-blocks/SectionTitleBlock';
|
||||
import ContentListBlock from '@/components/content-blocks/ContentListBlock';
|
||||
import ModuleRoomSlug from '@/components/content-blocks/ModuleRoomSlug';
|
||||
import Assignment from '@/components/content-blocks/assignment/Assignment';
|
||||
import Survey from '@/components/content-blocks/SurveyBlock';
|
||||
import Solution from '@/components/content-blocks/Solution';
|
||||
import Instruction from '@/components/content-blocks/Instruction';
|
||||
import BookmarkActions from '@/components/notes/BookmarkActions';
|
||||
|
||||
import {constructContentComponentBookmarkMutation} from '@/helpers/update-content-bookmark-mutation';
|
||||
|
||||
const TextBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/TextBlock');
|
||||
const InstrumentWidget = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/InstrumentWidget');
|
||||
const ImageBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ImageBlock');
|
||||
const ImageUrlBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ImageUrlBlock');
|
||||
const VideoBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/VideoBlock');
|
||||
const LinkBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/LinkBlock');
|
||||
const DocumentBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/DocumentBlock');
|
||||
const InfogramBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/InfogramBlock');
|
||||
const ThinglinkBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ThinglinkBlock');
|
||||
const GeniallyBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/GeniallyBlock');
|
||||
const SubtitleBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/SubtitleBlock');
|
||||
const SectionTitleBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/SectionTitleBlock');
|
||||
const ContentListBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ContentListBlock');
|
||||
const ModuleRoomSlug = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ModuleRoomSlug');
|
||||
const Assignment = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/assignment/Assignment');
|
||||
const Survey = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/SurveyBlock');
|
||||
const Solution = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/Solution');
|
||||
const Instruction = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/Instruction');
|
||||
const BookmarkActions = () => import(/* webpackChunkName: "content-components" */'@/components/notes/BookmarkActions');
|
||||
|
||||
export default {
|
||||
props: ['component', 'parent', 'bookmarks', 'notes', 'root'],
|
||||
|
||||
|
|
@ -92,13 +94,12 @@ export default {
|
|||
|
||||
methods: {
|
||||
addNote(id) {
|
||||
if (!this.parent.hasOwnProperty('__typename')) {
|
||||
this.parent.__typename = 'ContentBlockNode';
|
||||
}
|
||||
const type = Object.prototype.hasOwnProperty.call(this.parent, '__typename')
|
||||
? this.parent.__typename : 'ContentBlockNode';
|
||||
|
||||
this.$store.dispatch('addNote', {
|
||||
content: id,
|
||||
type: this.parent.__typename,
|
||||
type,
|
||||
block: this.root
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<ol class="content-list">
|
||||
<li
|
||||
class="content-list__item"
|
||||
v-for="(item, index) in items"
|
||||
:key="item.id"
|
||||
>
|
||||
<slot
|
||||
:item="item"
|
||||
:index="index"
|
||||
>
|
||||
{{ item.id }}
|
||||
</slot>
|
||||
</li>
|
||||
</ol>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
//
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,32 +1,26 @@
|
|||
<template>
|
||||
<div class="content-list-block__container">
|
||||
<div class="content-list-wrapper">
|
||||
<ol class="content-list">
|
||||
<li
|
||||
:key="contentBlock.id"
|
||||
class="content-list__item contentlist-item"
|
||||
v-for="(contentBlock, index) in contentBlocks">
|
||||
<p class="content-list__numbering">{{ alphaIndex(index) }})</p>
|
||||
<content-list
|
||||
:items="contentBlocks"
|
||||
:starting-index="startingIndex"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<content-block
|
||||
:content-block="contentBlock"
|
||||
:content-block="item"
|
||||
:parent="parent"
|
||||
/>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</content-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const lowerAsciiA = 97;
|
||||
|
||||
import ContentList from '@/components/content-blocks/ContentList';
|
||||
export default {
|
||||
name: 'ContentBlockList',
|
||||
props: ['contents', 'parent', 'startingIndex'],
|
||||
|
||||
components: {
|
||||
ContentList,
|
||||
// https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
|
||||
ContentBlock: () => import('@/components/ContentBlock')
|
||||
},
|
||||
|
|
@ -34,8 +28,9 @@
|
|||
computed: {
|
||||
contentBlocks() {
|
||||
return this.contents.map(contentBlock => {
|
||||
const contents = contentBlock.value ? [...contentBlock.value] : [];
|
||||
return Object.assign({}, contentBlock, {
|
||||
contents: [...contentBlock.value],
|
||||
contents,
|
||||
indent: true,
|
||||
bookmarks: this.parent.bookmarks,
|
||||
notes: this.parent.notes,
|
||||
|
|
@ -45,36 +40,10 @@
|
|||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
alphaIndex(index) {
|
||||
return String.fromCharCode(lowerAsciiA + this.startingIndex + index);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.content-list-wrapper {
|
||||
.content-list {
|
||||
/* https://stackoverflow.com/questions/1632005/ordered-list-html-lower-alpha-with-right-parentheses */
|
||||
|
||||
&__item {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0 0 0 2*15px;
|
||||
}
|
||||
|
||||
&__numbering {
|
||||
position: absolute;
|
||||
font-weight: 600;
|
||||
left: 0;
|
||||
color: $color-brand;
|
||||
line-height: 27px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@import "~styles/helpers";
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
<template>
|
||||
<div class="document-block">
|
||||
<document-icon class="document-block__icon"/>
|
||||
<document-icon class="document-block__icon" />
|
||||
<a
|
||||
:href="value.url"
|
||||
class="document-block__link"
|
||||
target="_blank">{{ urlName }}</a>
|
||||
target="_blank"
|
||||
>{{ urlName }}</a>
|
||||
<a
|
||||
class="document-block__remove"
|
||||
v-if="showTrashIcon"
|
||||
@click="$emit('trash')">
|
||||
<trash-icon class="document-block__trash-icon"/>
|
||||
@click="$emit('trash')"
|
||||
>
|
||||
<trash-icon class="document-block__trash-icon" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DocumentIcon from '@/components/icons/DocumentIcon';
|
||||
import TrashIcon from '@/components/icons/TrashIcon';
|
||||
const DocumentIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/DocumentIcon');
|
||||
const TrashIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/TrashIcon');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
allowscriptaccess="always"
|
||||
allowfullscreen="true"
|
||||
scrolling="yes"
|
||||
allownetworking="all"/>
|
||||
allownetworking="all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
:src="value.path"
|
||||
alt=""
|
||||
class="image-block"
|
||||
@click="openFullscreen">
|
||||
@click="openFullscreen"
|
||||
>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
:src="value.url"
|
||||
alt=""
|
||||
class="image-block"
|
||||
@click="openFullscreen">
|
||||
@click="openFullscreen"
|
||||
>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
class="infogram-block__iframe"
|
||||
scrolling="no"
|
||||
frameborder="0"
|
||||
style="border:none;"/>
|
||||
style="border:none;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
<template>
|
||||
<div
|
||||
class="instruction"
|
||||
v-if="me.isTeacher">
|
||||
<bulb-icon class="instruction__icon"/>
|
||||
v-if="me.isTeacher"
|
||||
>
|
||||
<bulb-icon class="instruction__icon" />
|
||||
<a
|
||||
:href="value.url"
|
||||
class="instruction__link">{{ text }}</a>
|
||||
class="instruction__link"
|
||||
>{{ text }}</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import me from '@/mixins/me';
|
||||
import BulbIcon from '@/components/icons/BulbIcon';
|
||||
const BulbIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/BulbIcon');
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div class="instrument-widget">
|
||||
<div
|
||||
class="instrument-widget__description"
|
||||
v-html="value.description"/>
|
||||
v-html="value.description"
|
||||
/>
|
||||
<router-link
|
||||
:to="{name: 'instrument', params: { slug: value.slug }}"
|
||||
class="instrument-widget__button button">Instrument anzeigen
|
||||
class="instrument-widget__button button"
|
||||
>
|
||||
Instrument anzeigen
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{ 'link-block--no-margin': noMargin}"
|
||||
class="link-block">
|
||||
<link-icon class="link-block__icon"/>
|
||||
class="link-block"
|
||||
>
|
||||
<link-icon class="link-block__icon" />
|
||||
<a
|
||||
:href="href"
|
||||
class="link-block__link"
|
||||
target="_blank">{{ value.text }}</a>
|
||||
target="_blank"
|
||||
>{{ value.text }}</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LinkIcon from '@/components/icons/LinkIcon';
|
||||
const LinkIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/LinkIcon');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: Object,
|
||||
'no-margin': {
|
||||
noMargin: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
<div class="module-slug">
|
||||
<router-link
|
||||
:to="{name: 'moduleRoom', params: { slug: value.slug }}"
|
||||
class="button button--primary">Raum anzeigen
|
||||
class="button button--primary"
|
||||
>
|
||||
Raum anzeigen
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<h4
|
||||
class="section-title"
|
||||
v-html="value.text"/>
|
||||
v-html="value.text"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
<template>
|
||||
<div
|
||||
class="solution"
|
||||
data-cy="solution">
|
||||
data-cy="solution"
|
||||
>
|
||||
<a
|
||||
class="solution__toggle"
|
||||
data-cy="show-solution"
|
||||
@click="toggle">Lösung
|
||||
@click="toggle"
|
||||
>Lösung
|
||||
<template v-if="!visible">anzeigen</template>
|
||||
<template v-else>ausblenden</template>
|
||||
</a>
|
||||
|
|
@ -15,7 +17,8 @@
|
|||
data-cy="solution-text"
|
||||
|
||||
v-if="visible"
|
||||
v-html="value.text"/>
|
||||
v-html="value.text"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<h5
|
||||
class="subtitle"
|
||||
v-html="value.text"/>
|
||||
v-html="value.text"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//todo: esacpe value.text
|
||||
export default {
|
||||
props: ['value']
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
<template>
|
||||
<div
|
||||
:data-scrollto="value.id"
|
||||
class="survey-block">
|
||||
class="survey-block"
|
||||
>
|
||||
<router-link
|
||||
:to="{name: 'survey', params: {id:value.id}}"
|
||||
class="button button--primary">Übung anzeigen
|
||||
class="button button--primary"
|
||||
>
|
||||
Übung anzeigen
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div class="task">
|
||||
<div
|
||||
class="task__text"
|
||||
v-html="value.text"/>
|
||||
v-html="value.text"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<div
|
||||
class="text-block"
|
||||
v-html="value.text"/>
|
||||
v-html="value.text"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// todo: escape text maybe
|
||||
export default {
|
||||
props: ['value']
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
scrolling="no"
|
||||
allowscriptaccess="always"
|
||||
allowfullscreen="true"
|
||||
allownetworking="all"/>
|
||||
allownetworking="all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@
|
|||
<div class="video-block">
|
||||
<youtube-embed
|
||||
:url="value.url"
|
||||
v-if="isYoutube"/>
|
||||
v-if="isYoutube"
|
||||
/>
|
||||
<vimeo-embed
|
||||
:url="value.url"
|
||||
v-if="isVimeo"/>
|
||||
v-if="isVimeo"
|
||||
/>
|
||||
<srf-embed
|
||||
:url="value.url"
|
||||
v-if="isSrf"/>
|
||||
|
||||
v-if="isSrf"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<div
|
||||
:data-scrollto="value.id"
|
||||
class="assignment">
|
||||
class="assignment"
|
||||
>
|
||||
<p class="assignment__assignment-text">
|
||||
{{ assignment.assignment }}
|
||||
</p>
|
||||
|
||||
<solution
|
||||
:value="solution"
|
||||
v-if="assignment.solution"/>
|
||||
v-if="assignment.solution"
|
||||
/>
|
||||
|
||||
<template v-if="isStudent">
|
||||
<submission-form
|
||||
|
|
@ -30,17 +32,21 @@
|
|||
|
||||
<spell-check
|
||||
:corrections="corrections"
|
||||
:text="submission.text"/>
|
||||
:text="submission.text"
|
||||
/>
|
||||
|
||||
<p
|
||||
class="assignment__feedback"
|
||||
v-if="assignment.submission.submissionFeedback"
|
||||
v-html="feedbackText"/>
|
||||
v-html="feedbackText"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="!isStudent">
|
||||
<router-link
|
||||
:to="{name: 'submissions', params: { id: assignment.id }}"
|
||||
class="button button--primary">Zu den
|
||||
class="button button--primary"
|
||||
>
|
||||
Zu den
|
||||
Ergebnissen
|
||||
</router-link>
|
||||
</template>
|
||||
|
|
@ -57,9 +63,9 @@
|
|||
import debounce from 'lodash/debounce';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
import SubmissionForm from '@/components/content-blocks/assignment/SubmissionForm';
|
||||
import Solution from '@/components/content-blocks/Solution';
|
||||
import SpellCheck from '@/components/content-blocks/assignment/SpellCheck';
|
||||
const SubmissionForm = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/assignment/SubmissionForm');
|
||||
const Solution = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/Solution');
|
||||
const SpellCheck = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/assignment/SpellCheck');
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
|
|
@ -106,6 +112,7 @@
|
|||
return this.assignment.id ? this.assignment.id.replace(/=/g, '') : '';
|
||||
},
|
||||
feedbackText() {
|
||||
// todo: should we maybe clean up this feedback text?
|
||||
let feedback = this.assignment.submission.submissionFeedback;
|
||||
return `<span class="inline-title">Feedback von ${feedback.teacher.firstName} ${feedback.teacher.lastName}:</span> ${feedback.text}`;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
<template>
|
||||
<div
|
||||
class="final-submission"
|
||||
data-cy="final-submission">
|
||||
data-cy="final-submission"
|
||||
>
|
||||
<document-block
|
||||
:value="{url: userInput.document}"
|
||||
class="final-submission__document"
|
||||
v-if="userInput.document"
|
||||
/>
|
||||
<div class="final-submission__explanation">
|
||||
<info-icon class="final-submission__explanation-icon"/>
|
||||
<info-icon class="final-submission__explanation-icon" />
|
||||
<span class="final-submission__explanation-text">{{ sharedMsg }}</span>
|
||||
<a
|
||||
class="final-submission__reopen"
|
||||
data-cy="final-submission-reopen"
|
||||
v-if="showReopen"
|
||||
@click="$emit('reopen')">Bearbeiten</a>
|
||||
@click="$emit('reopen')"
|
||||
>Bearbeiten</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InfoIcon from '@/components/icons/InfoIcon';
|
||||
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
|
||||
import {newLineToParagraph} from '@/helpers/text';
|
||||
const DocumentBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/DocumentBlock');
|
||||
|
||||
const InfoIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/InfoIcon');
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<p
|
||||
class="spellcheck"
|
||||
v-if="corrections">
|
||||
<span class="inline-title">Rechtschreibung:</span> <span v-html="highlightedText"/>
|
||||
v-if="corrections"
|
||||
>
|
||||
<span class="inline-title">Rechtschreibung:</span> <span v-html="highlightedText" />
|
||||
</p>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,25 +12,29 @@
|
|||
|
||||
<div
|
||||
class="submission-form-container__actions"
|
||||
v-if="!isFinalOrReadOnly">
|
||||
v-if="!isFinalOrReadOnly"
|
||||
>
|
||||
<button
|
||||
class="submission-form-container__submit button button--primary button--white-bg"
|
||||
data-cy="submission-form-submit"
|
||||
@click="$emit('turnIn')"
|
||||
>{{ action }}
|
||||
>
|
||||
{{ action }}
|
||||
</button>
|
||||
<button
|
||||
class="submission-form-container__submit submission-form-container__spellcheck button button--primary button--white-bg"
|
||||
data-cy="spellcheck-button"
|
||||
v-if="showSpellcheckButton"
|
||||
@click="$emit('spellcheck')"
|
||||
>{{ spellcheckText }}
|
||||
>
|
||||
{{ spellcheckText }}
|
||||
</button>
|
||||
<file-upload
|
||||
:document="userInput.document"
|
||||
v-if="allowsDocuments"
|
||||
@change-document-url="changeDocumentUrl"/>
|
||||
<slot/>
|
||||
@change-document-url="changeDocumentUrl"
|
||||
/>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<final-submission
|
||||
|
|
@ -38,15 +42,16 @@
|
|||
:shared-msg="sharedMsg"
|
||||
:show-reopen="!readOnly"
|
||||
v-if="isFinalOrReadOnly"
|
||||
@reopen="$emit('reopen')"/>
|
||||
@reopen="$emit('reopen')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SubmissionInput from '@/components/content-blocks/assignment/SubmissionInput';
|
||||
import FinalSubmission from '@/components/content-blocks/assignment/FinalSubmission';
|
||||
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
|
||||
import FileUpload from '@/components/ui/file-upload/FileUpload';
|
||||
const SubmissionInput = () => import('@/components/content-blocks/assignment/SubmissionInput');
|
||||
const FinalSubmission = () => import('@/components/content-blocks/assignment/FinalSubmission');
|
||||
const FileUpload = () => import('@/components/ui/file-upload/FileUpload');
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -75,7 +80,6 @@
|
|||
FileUpload,
|
||||
SubmissionInput,
|
||||
FinalSubmission,
|
||||
DocumentBlock,
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue