Todas las cosas

This commit is contained in:
Cat /dev/Nulo 2021-10-11 17:25:55 -03:00
commit 07d23b5971
413 changed files with 42587 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

1
0.0c5b2e6a.chunk.js.map Normal file

File diff suppressed because one or more lines are too long

1
6.776c1e8b.chunk.js.map Normal file

File diff suppressed because one or more lines are too long

1
7.c65299ef.chunk.css.map Normal file

File diff suppressed because one or more lines are too long

1
7.f024fa6c.chunk.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"static/js/7.f024fa6c.chunk.js","sourceRoot":""}

1
8.16508763.chunk.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"static/js/8.16508763.chunk.js","sourceRoot":""}

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# [Arreglando bugs ajenos](https://nulo.in/Arreglando%20bugs%20ajenos.html)
Pista: el código está en <sources-gen/>. Añadí la config de nginx de learngerman.nulo.in en <learngerman.nulo.in.conf>.

47
learngerman.nulo.in.conf Normal file
View File

@ -0,0 +1,47 @@
server {
ssl_certificate /etc/letsencrypt/live/learngerman.nulo.in/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/learngerman.nulo.in/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/learngerman.nulo.in/chain.pem;
include snippets/https.conf;
server_name learngerman.nulo.in;
root /var/www/learngerman.nulo.in/;
location / {
index index.html;
try_files $uri /index.html =404;
}
location ~ ^(\/graphql|\/auth\/login|\/profiles) {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
}
proxy_pass https://learngerman.dw.com;
}
include snippets/acme.conf;
}

4
links Normal file
View File

@ -0,0 +1,4 @@
https://learngerman.dw.com/static/css/main.f50335e4.chunk.css.map
https://learngerman.dw.com/static/css/7.c65299ef.chunk.css.map
https://learngerman.dw.com/static/css/playerPlugins-video.4cb21404.chunk.css.map
https://learngerman.dw.com/static/css/playerPlugins-audio.dcc31119.chunk.css.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

38
package.json Normal file
View File

@ -0,0 +1,38 @@
{
"dependencies": {
"@apollo/client": "^3.4.16",
"classnames": "^2.3.1",
"crypto-hash": "^1.3.0",
"graphql": "^15.6.1",
"graphql-tag": "^2.12.5",
"minimist": "^1.2.5",
"mkdirp": "^1.0.4",
"react": "^17.0.2",
"react-beforeunload": "^2.5.2",
"react-dom": "^17.0.2",
"react-google-recaptcha": "^2.1.0",
"react-helmet-async": "^1.1.2",
"react-hook-form": "^7.17.2",
"react-redux": "^7.2.5",
"react-router": "^5.2.1",
"react-router-dom": "^5.3.0",
"react-simple-keyboard": "^3.2.104",
"redux": "^4.1.1",
"redux-storage": "https://github.com/react-stack/redux-storage",
"redux-storage-engine-localstorage": "^1.1.4",
"redux-thunk": "^2.3.0",
"simple-keyboard-layouts": "^3.0.197",
"source-map": "^0.7.3",
"styled-components": "^5.3.1",
"stylis": "^4.0.10",
"stylis-plugin-rtl": "^2.1.0",
"video.js": "^7.15.4",
"videojs-contrib-quality-levels": "^2.1.0",
"videojs-hls-quality-selector": "^1.1.4",
"videojs-seek-buttons": "^2.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^1.0.2",
"vite": "^2.6.5"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"sources":["webpack://node_modules/videojs-seek-buttons/dist/videojs-seek-buttons.css"],"names":[],"mappings":"AAAA,mEAAmE,CACnE,2BAA2B,qBAAqB,CAAC,cAAc,CAAC,eAAe,CAAC,iBAAiB,CAAC,8LAAiM,wBAAwB,CAAC,4BAA4B,CAAC,gCAAgC,CAAC,eAAe,CAAC,+CAAgD,wCAAwC,CAAC,4CAA4C,CAAC,gDAAgD,CAAC,eAAe,CAAC,kNAAsN,YAAY,CAAC,wJAA0J,mCAAoC,CAAC,uCAAwC,CAAC,2CAA4C,CAAC,eAAe","file":"playerPlugins-audio.dcc31119.chunk.css","sourcesContent":["/*! @name videojs-seek-buttons @version 2.0.1 @license Apache-2.0 */\n.video-js .vjs-seek-button{font-family:'VideoJS';cursor:pointer;font-weight:400;font-style:normal}.video-js .vjs-seek-button.skip-back::before,.video-js.vjs-v6 .vjs-seek-button.skip-back .vjs-icon-placeholder::before,.video-js.vjs-v7 .vjs-seek-button.skip-back .vjs-icon-placeholder::before{transform:rotate(-45deg);-ms-transform:rotate(-45deg);-webkit-transform:rotate(-45deg);content:'\\f116'}.video-js .vjs-seek-button.skip-forward::before{transform:rotateY(180deg) rotate(-45deg);-ms-transform:rotateY(180deg) rotate(-45deg);-webkit-transform:rotateY(180deg) rotate(-45deg);content:'\\f116'}.video-js.vjs-v6 .vjs-seek-button.skip-back::before,.video-js.vjs-v6 .vjs-seek-button.skip-forward::before,.video-js.vjs-v7 .vjs-seek-button.skip-back::before,.video-js.vjs-v7 .vjs-seek-button.skip-forward::before{content:none}.video-js.vjs-v6 .vjs-seek-button.skip-forward .vjs-icon-placeholder::before,.video-js.vjs-v7 .vjs-seek-button.skip-forward .vjs-icon-placeholder::before{transform:scale(-1,1) rotate(-45deg);-ms-transform:scale(-1,1) rotate(-45deg);-webkit-transform:scale(-1,1) rotate(-45deg);content:'\\f116'}"]}

View File

@ -0,0 +1 @@
{"version":3,"sources":["webpack://node_modules/videojs-hls-quality-selector/dist/videojs-hls-quality-selector.css"],"names":[],"mappings":"AAMA,mCAAmC,aAAa","file":"playerPlugins-video.4cb21404.chunk.css","sourcesContent":["/**\n * videojs-hls-quality-selector\n * @version 1.1.4\n * @copyright 2020 Chris Boustead (chris@forgemotion.com)\n * @license MIT\n */\n.video-js.vjs-hls-quality-selector{display:block}\n"]}

File diff suppressed because one or more lines are too long

1689
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

47
source-from-sourcemaps.js Normal file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env node
const fs = require("fs");
const util = require("util");
const writeFileP = util.promisify(fs.writeFile);
const readFileP = util.promisify(fs.readFile);
const mkdirp = require("mkdirp");
const path = require("path");
const { SourceMapConsumer } = require("source-map");
const minimist = require("minimist");
async function processSource(consumer, src) {
const fsSrc =
"sources-gen/" +
src
.replace(/webpack:\/\/\//, "")
.replace(/~\//, "node_modules/")
.replace(/\?.+$/, "");
const source = consumer.sourceContentFor(src, true);
if (!source) {
console.warn("Unable to source:", src);
return;
}
await mkdirp(path.dirname(fsSrc));
await writeFileP(fsSrc, source, "UTF-8");
console.log(`Wrote ${fsSrc}`);
}
async function processFile(pth) {
const data = await readFileP(pth, "UTF-8");
return await SourceMapConsumer.with(data, null, async (consumer) => {
const processors = consumer.sources.map((src) =>
processSource(consumer, src)
);
return await Promise.all(processors);
});
}
async function main() {
const args = minimist(process.argv.slice(2));
const processors = args._.map((pth) => processFile(pth));
return await Promise.all(processors);
}
if (!module.parent) {
main().then(() => process.exit(0));
}

View File

@ -0,0 +1,14 @@
{
"0 debug pnpm:scope": {
"selected": 1
},
"1 error pnpm": {
"code": "ERR_PNPM_NO_SCRIPT",
"err": {
"name": "pnpm",
"message": "Missing script: vite",
"code": "ERR_PNPM_NO_SCRIPT",
"stack": "pnpm: Missing script: vite\n at Object.handler (/home/nulo/.npm/prefix/lib/node_modules/pnpm/dist/pnpm.cjs:129183:15)\n at async /home/nulo/.npm/prefix/lib/node_modules/pnpm/dist/pnpm.cjs:133329:20\n at async run (/home/nulo/.npm/prefix/lib/node_modules/pnpm/dist/pnpm.cjs:133304:34)\n at async runPnpm (/home/nulo/.npm/prefix/lib/node_modules/pnpm/dist/pnpm.cjs:133514:5)\n at async /home/nulo/.npm/prefix/lib/node_modules/pnpm/dist/pnpm.cjs:133506:7"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,304 @@
.override-rtl{
direction:rtl;
}
.monolingual-course{
padding-left: 20px;
padding-right: 0;
}
.override-ltr{
direction:ltr;
}
span.ltr{
left:0 !important;
right:inherit !important;
}
.override-strong strong{
font-weight: 700 !important;
}
.tt-suggestion{
padding-bottom: 10px;
padding-top: 10px;
}
@media (min-width: 768px) and (max-width: 991px) {
.override-ltr .col-lg-1,
.override-ltr .col-lg-10,
.override-ltr .col-lg-11,
.override-ltr .col-lg-12,
.override-ltr .col-lg-2,
.override-ltr .col-lg-3,
.override-ltr .col-lg-4,
.override-ltr .col-lg-5,
.override-ltr .col-lg-6,
.override-ltr .col-lg-7,
.override-ltr .col-lg-8,
.override-ltr .col-lg-9 {
float: left !important;
}
.override-ltr .col-lg-offset-2 {
margin-right: 0 !important;
}
.override-ltr .col-sm-offset-1 {
margin-left: 8.33333333%;
margin-right: 0;
}
}
@media (min-width: 992px) {
.override-ltr .col-lg-1,
.override-ltr .col-lg-10,
.override-ltr .col-lg-11,
.override-ltr .col-lg-12,
.override-ltr .col-lg-2,
.override-ltr .col-lg-3,
.override-ltr .col-lg-4,
.override-ltr .col-lg-5,
.override-ltr .col-lg-6,
.override-ltr .col-lg-7,
.override-ltr .col-lg-8,
.override-ltr .col-lg-9 {
float: left!important;
}
.override-ltr .col-lg-offset-2 {
margin-right: 0!important;
margin-left: 16.66666667%!important;
}
.override-ltr .col-sm-offset-1 {
margin-left: 8.33333333%;
margin-right: 0;
}
}
.override-ltr div.row.vocabulary div.col-sm-3 p {
margin-right: 30px!important;
margin-left: auto!important;
}
.override-ltr a.audio-link:before {
background-position: left top;
margin-right: 8px;
margin-left: 0;
}
.consonants:hover {
border-bottom: 2px solid #23527c!important;
}
.consonants {
border-bottom: 1px solid #23527c!important;
color: #23527c!important;
}
.container.page-container {
overflow: visible;
}
div#header-row div.row-menu div.lecture-nav-container div.navbar nav.lecture-nav .navbar-toggle:focus .icon-close-cross {
background: url(../svg/nav-cross-black-lg.svg) no-repeat;
background-size: cover;
}
header div.row-menu div.navbar.navbar-default.navbar-static-top nav.main-nav .navbar-toggle:focus .close-cross {
background: url(../svg/nav-cross-xs.svg) no-repeat;
background-size: cover;
}
div#the-basics.rtl span.twitter-typeahead {
width: 100%;
}
div#the-basics.rtl span.twitter-typeahead .input-lg{
padding-left: 20px;
direction: rtl;
}
div#the-basics.rtl span.twitter-typeahead input[type="search"].tt-input {
background-image: url(../svg/icon-lupe.svg);
background-repeat: no-repeat;
padding-left: 20px;
padding-right: 44px;
direction: rtl;
background-position: 7% 14px;
}
span.course-score-ltr,
button.course-reset-ltr {
left: inherit !important;
right: 15px !important;
}
#header-row {
margin-bottom: 58px;
}
@media (min-width: 768px) {
div#the-basics.rtl span.twitter-typeahead input[type="search"].tt-input {
background-position: 3% 14px;
}
span.course-score-ltr,
button.course-reset-ltr {
left: inherit !important;
right: 56px !important;
}
#header-row {
margin-bottom: 58px;
}
}
@media (min-width: 992px) {
div#the-basics.rtl span.twitter-typeahead input[type="search"].tt-input {
background-position: 2% 14px;
}
span.course-score-ltr,
button.course-reset-ltr {
left: inherit !important;
right: 20px !important;
}
#header-row {
margin-bottom: 84px;
}
}
.richtext-content-container img {
max-width: 300px;
}
#newsletter-subscribe, #newsletter-unsubscribe {
display: none;
}
.user-profile-button {
padding-top: 8px;
}
.need-help-link {
padding-top:20px;
padding-bottom:10px;
}
.cookie {
position: fixed;
bottom: 0;
width: 100%;
background: rgba(59, 68, 77, 0.9);
z-index: 1000;
font: normal normal 400 12px/18px Arial, Helvetica, sans-serif;
}
.cookie--visible > div {
transition: 350ms 250ms ease-out;
transform: translateY(0%);
}
.cookie__wrap {
width: 940px;
color: rgb(255, 255, 255);
margin: 18px auto;
display: -webkit-box;
display: -webkit-flex;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
-moz-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
letter-spacing: 0.5px;
}
@media (max-width: 940px) {
.cookie__wrap {
width: auto;
margin: 18px 18px;
flex-direction: column;
-webkit-box-direction: normal;
-webkit-box-orient: vertical;
-moz-box-direction: normal;
-moz-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
}
.cookie__text {
-ms-align-self: flex-start;
-webkit-align-self: flex-start;
-moz-align-self: flex-start;
-ms-flex-item-align: start;
align-self: flex-start;
}
.cookie__buttons {
-ms-align-self: flex-end;
-webkit-align-self: flex-end;
-moz-align-self: flex-end;
-ms-flex-item-align: end;
align-self: flex-end;
}
}
.cookie__item {
-ms-flex-item-align: stretch;
-webkit-align-self: stretch;
-moz-align-self: stretch;
align-self: stretch;
}
.cookie__text {
font-size: 12px;
font-weight: 400;
}
.cookie__buttons {
display: -webkit-box;
display: -webkit-flex;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
-moz-justify-content: space-between;
justify-content: space-between;
}
.cookie__buttons a {
border: none;
color: rgb(255, 255, 255);
padding: 7px 14px 8px;
font-weight: 700;
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
white-space: nowrap;
}
a.cookie__btn--more {
background-color: rgb(127, 136, 145);
margin: 0 8px;
}
a.cookie__btn--more:hover {
background-color: rgb(216, 221, 226);
color: rgb(59, 68, 77);
text-decoration: none;
}
a.cookie__btn--ok {
background-color: rgb(0, 165, 255);
}
a.cookie__btn--ok:hover {
background-color: rgb(255, 255, 255);
color: rgb(0, 165, 255);
text-decoration: none;
}
a.glossar-modal.custom-modal:focus {
border: 2px solid #0098FF;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/dwtheantiquab-w5plain.94c5b804.woff";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/dwtheantiquab-w5plain.524a5eac.woff2";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/DWTheAntiquaB-W5PlainItalic.0c806fe1.woff";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/DWTheAntiquaB-W5PlainItalic.390937af.woff2";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/DWTheAntiquaB-W7Bold.73a35783.woff";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/DWTheAntiquaB-W7Bold.c739cf0d.woff2";

View File

@ -0,0 +1,4 @@
import styled from 'styled-components';
import { ReactComponent as ClaimLogo } from '../svg/dw-claim.svg';
export const StyledClaimLogo = styled(props => <ClaimLogo {...props} />)``;

View File

@ -0,0 +1,4 @@
import styled from 'styled-components';
import { ReactComponent as Logo } from '../svg/dw-logo.svg';
export const StyledLogo = styled(props => <Logo {...props} />)``;

View File

@ -0,0 +1,6 @@
import styled from 'styled-components';
import { ReactComponent as SmileyPositive } from '../svg/smiley-positive.svg';
import { ReactComponent as SmileyNegative } from '../svg/smiley-negative.svg';
export const StyledSmileyPositive = styled(SmileyPositive)``;
export const StyledSmileyNegative = styled(SmileyNegative)``;

View File

@ -0,0 +1,4 @@
import styled from 'styled-components';
import { ReactComponent as ToggleableArrowIcon } from '../svg/toggleableArrow.svg';
export const StyledToggleableArrowIcon = styled(props => <ToggleableArrowIcon {...props} />)``;

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/bg-dw-desktop.4aec073f.jpg";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/course_landing-1200x675.f68dc5b5.jpg";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/course_landing-480x270.5a82869e.jpg";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/course_landing-768x432.d54bc099.jpg";

View File

@ -0,0 +1 @@
export default __webpack_public_path__ + "static/media/course_landing-992x558.4309e861.jpg";

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/dw-logo-mobile.7e6e58ec.svg";

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/dw-logo-tablet.ac032dbb.svg";

View File

@ -0,0 +1,37 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
var _ref2 = /*#__PURE__*/React.createElement("g", {
className: "logoBox",
fillRule: "evenodd"
}, /*#__PURE__*/React.createElement("g", {
className: "logoLogo"
}, /*#__PURE__*/React.createElement("path", {
d: "M15.5929091,3.39736364 C22.287,3.39736364 27.714,8.82381818 27.714,15.5184545 C27.714,22.2125455 22.287,27.6392727 15.5929091,27.6392727 C8.89881818,27.6392727 3.47181818,22.2125455 3.47181818,15.5184545 C3.47181818,8.82381818 8.89881818,3.39736364 15.5929091,3.39736364 L15.5929091,3.39736364 Z M26.9959091,5.16463636 C29.8276364,1.99527273 33.9458182,0 38.5303636,0 C47.0694545,0 53.9920909,6.92236364 53.9920909,15.4617273 C53.9920909,24.0010909 47.0694545,30.9234545 38.5303636,30.9234545 C33.9458182,30.9234545 29.8276364,28.9281818 26.9959091,25.7585455 C24.1644545,28.9281818 20.046,30.9234545 15.4614545,30.9234545 C6.92209091,30.9234545 0,24.0010909 0,15.4617273 C0,6.92236364 6.92209091,1.93784382e-15 15.4614545,1.93784382e-15 C20.046,1.93784382e-15 24.1644545,1.99527273 26.9959091,5.16463636 L26.9959091,5.16463636 Z M15.9913636,13.0819091 L12.6553636,13.0819091 L12.6553636,18.0845455 L15.9913636,18.0845455 C18.4150909,18.0845455 19.2714545,17.0787273 19.2714545,15.5833636 C19.2714545,14.088 18.4085455,13.0819091 15.9913636,13.0819091 L15.9913636,13.0819091 Z M16.4511818,9.59536364 C19.8250909,9.59536364 23.2835455,11.292 23.2835455,15.5828182 C23.2835455,19.8739091 19.8250909,21.5705455 16.4511818,21.5705455 L8.886,21.5705455 L8.886,9.59536364 L16.4511818,9.59536364 L16.4511818,9.59536364 Z M35.4119983,15.9318147 L37.5403715,10.1204985 L40.4334297,10.1204985 L42.5618029,15.9318147 L44.2678615,10.1204985 L48.0692727,10.1204985 L44.4908946,21.3263197 L41.2676315,21.3263197 L38.9674938,15.9016908 L36.7070387,21.3263197 L33.483486,21.3263197 L29.9048182,10.1204985 L33.7062294,10.1204985 L35.4119983,15.9318147 L35.4119983,15.9318147 Z",
id: "Shape"
})));
function SvgDwLogo(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
viewBox: "0 0 54 31",
xmlns: "http://www.w3.org/2000/svg",
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, _ref2);
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgDwLogo);
export default __webpack_public_path__ + "static/media/dw-logo.ff9012af.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/icon-course-cert-gray.0fdcab0c.svg";

View File

@ -0,0 +1,42 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
function SvgIconHintCircleOrange(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
width: "18px",
height: "18px",
viewBox: "0 0 18 18",
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink",
xmlSpace: "preserve",
style: {
fillRule: "evenodd",
clipRule: "evenodd",
strokeLinejoin: "round",
strokeMiterlimit: 1.41421
},
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, /*#__PURE__*/React.createElement("path", {
d: "M9,0C13.967,0 18,4.033 18,9C18,13.967 13.967,18 9,18C4.033,18 0,13.967 0,9C0,4.033 4.033,0 9,0ZM12.189,14.994L12.189,14.343L10.027,13.623L10.027,6.966C8.269,6.966 7.525,7.069 6.206,7.289L6.206,7.903C7.113,8.041 7.538,8.144 7.977,8.337L7.977,13.657C7.181,13.849 6.502,14.013 5.87,14.343L5.87,14.994L12.189,14.994ZM8.984,5.417C8.544,5.417 8.31,5.322 8.007,5.02C7.705,4.718 7.61,4.484 7.61,4.044C7.61,3.605 7.705,3.37 8.007,3.068C8.31,2.765 8.544,2.67 8.984,2.67C9.424,2.67 9.657,2.765 9.96,3.068C10.262,3.37 10.357,3.605 10.357,4.044C10.357,4.484 10.262,4.718 9.96,5.02C9.657,5.322 9.424,5.417 8.984,5.417Z",
style: {
fill: "#f08c00"
}
}));
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgIconHintCircleOrange);
export default __webpack_public_path__ + "static/media/icon-hint-circle-orange.764164ab.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,39 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
function SvgIconHint(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
width: "10px",
height: "18px",
viewBox: "0 0 10 18",
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink",
style: {
fillRule: "evenodd"
},
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, /*#__PURE__*/React.createElement("path", {
d: "M369.101562,1457.80899 L369.101562,1456.85041 C369.918135,1456.42437 370.841202,1456.08709 371.870793,1455.83857 L371.870793,1448.22319 C371.302743,1447.97467 370.432929,1447.76165 369.261326,1447.58413 L369.261326,1446.67881 C370.965476,1446.39478 372.953622,1446.25277 375.225823,1446.25277 L375.225823,1455.83857 L378.101562,1456.85041 L378.101562,1457.80899 L369.101562,1457.80899 Z M373.308663,1444.17585 C372.740613,1444.17585 372.261328,1443.98059 371.870793,1443.59005 C371.480259,1443.19952 371.284994,1442.72023 371.284994,1442.15218 C371.284994,1441.58413 371.480259,1441.10485 371.870793,1440.71431 C372.261328,1440.32378 372.740613,1440.12851 373.308663,1440.12851 C373.876713,1440.12851 374.355998,1440.32378 374.746533,1440.71431 C375.137067,1441.10485 375.332332,1441.58413 375.332332,1442.15218 C375.332332,1442.72023 375.137067,1443.19952 374.746533,1443.59005 C374.355998,1443.98059 373.876713,1444.17585 373.308663,1444.17585 L373.308663,1444.17585 Z",
style: {
fill: "#f08c00"
},
transform: "translate(-369.000000, -1440.000000)"
}));
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgIconHint);
export default __webpack_public_path__ + "static/media/icon-hint.484f8f25.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,43 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
function SvgIconLanguageWorldW(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
width: "16px",
height: "16px",
viewBox: "0 0 16 16",
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink",
xmlSpace: "preserve",
style: {
fillRule: "evenodd",
clipRule: "evenodd",
strokeLinejoin: "round",
strokeMiterlimit: 1.41421
},
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, /*#__PURE__*/React.createElement("path", {
d: "M7.976,0.026c-1.443,0 -2.784,0.36 -4.021,1.082c-1.208,0.707 -2.165,1.665 -2.872,2.873c-0.722,1.252 -1.083,2.599 -1.083,4.043c0,1.443 0.361,2.784 1.083,4.021c0.707,1.208 1.664,2.165 2.872,2.872c1.237,0.722 2.578,1.083 4.021,1.083c1.444,0 2.784,-0.361 4.021,-1.083c1.208,-0.707 2.166,-1.664 2.873,-2.872c0.736,-1.237 1.104,-2.578 1.104,-4.021c0,-1.444 -0.36,-2.784 -1.082,-4.021c-0.707,-1.208 -1.665,-2.166 -2.873,-2.873c-1.252,-0.736 -2.599,-1.104 -4.043,-1.104Zm3.005,6.451c1.208,-0.25 2.268,-0.641 3.182,-1.171c0.383,0.855 0.574,1.76 0.574,2.718c0,1.149 -0.272,2.209 -0.817,3.181c-0.973,-0.486 -1.974,-0.832 -3.005,-1.038c0.088,-0.692 0.132,-1.407 0.132,-2.143c0,-0.53 -0.022,-1.046 -0.066,-1.547Zm2.629,-2.165c-0.942,0.398 -1.878,0.677 -2.806,0.84c-0.25,-1.429 -0.648,-2.659 -1.193,-3.69c0.825,0.206 1.584,0.556 2.276,1.049c0.692,0.494 1.267,1.094 1.723,1.801Zm-3.734,3.712c0,0.781 -0.037,1.429 -0.11,1.944c-0.634,-0.073 -1.23,-0.11 -1.79,-0.11c-0.56,0 -1.156,0.037 -1.789,0.11c-0.074,-0.692 -0.111,-1.34 -0.111,-1.944c0,-0.309 0.015,-0.759 0.044,-1.348c0.516,0.074 1.138,0.111 1.867,0.111c0.729,0 1.344,-0.037 1.845,-0.111c0.03,0.589 0.044,1.039 0.044,1.348Zm-1.414,-6.739c0.589,1.178 0.995,2.526 1.215,4.043c-0.736,0.059 -1.303,0.089 -1.701,0.089c-0.398,0 -0.965,-0.03 -1.701,-0.089c0.236,-1.517 0.641,-2.865 1.215,-4.043c0.103,-0.015 0.265,-0.022 0.486,-0.022c0.221,0 0.383,0.007 0.486,0.022Zm-2.121,0.177c-0.56,1.09 -0.957,2.32 -1.193,3.69c-0.972,-0.192 -1.907,-0.472 -2.806,-0.84c0.471,-0.707 1.05,-1.307 1.734,-1.801c0.685,-0.493 1.44,-0.843 2.265,-1.049Zm-4.551,3.844c0.942,0.53 2.003,0.921 3.181,1.171c-0.044,0.501 -0.066,1.017 -0.066,1.547c0,0.736 0.044,1.451 0.133,2.143c-1.032,0.206 -2.033,0.552 -3.005,1.038c-0.531,-0.986 -0.796,-2.047 -0.796,-3.181c0,-0.943 0.184,-1.849 0.553,-2.718Zm0.906,6.894c0.824,-0.324 1.679,-0.56 2.563,-0.707c0.235,1.119 0.596,2.143 1.082,3.071c-0.722,-0.192 -1.395,-0.49 -2.021,-0.895c-0.627,-0.405 -1.168,-0.895 -1.624,-1.469Zm4.794,2.541c-0.486,-0.973 -0.854,-2.114 -1.105,-3.425c0.531,-0.044 1.068,-0.066 1.613,-0.066c0.545,0 1.068,0.022 1.569,0.066c-0.25,1.311 -0.619,2.452 -1.105,3.425c-0.103,0.014 -0.265,0.022 -0.486,0.022c-0.221,0 -0.383,-0.008 -0.486,-0.022Zm2.121,-0.177c0.486,-0.928 0.847,-1.952 1.083,-3.071c0.898,0.147 1.76,0.383 2.585,0.707c-0.471,0.589 -1.02,1.086 -1.646,1.491c-0.626,0.405 -1.3,0.696 -2.022,0.873Z",
style: {
fill: "#fff",
fillRule: "nonzero"
}
}));
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgIconLanguageWorldW);
export default __webpack_public_path__ + "static/media/icon-language-world-w.bb8502bb.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,53 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
var _ref2 = /*#__PURE__*/React.createElement("desc", null, "Person Icon");
var _ref3 = /*#__PURE__*/React.createElement("g", {
id: "Seiten",
stroke: "none",
strokeWidth: 1,
fill: "none",
fillRule: "evenodd",
opacity: 1
}, /*#__PURE__*/React.createElement("g", {
id: "06-desk-lektionsinfo+burger",
transform: "translate(-1146.000000, -501.000000)",
fill: "#FFFFFF"
}, /*#__PURE__*/React.createElement("g", {
id: "burgernavi-d",
transform: "translate(920.000000, 170.000000)"
}, /*#__PURE__*/React.createElement("path", {
d: "M241.86087,345.655072 C239.603853,344.603859 238.150728,344.078261 237.501449,344.078261 L233.976812,345.284058 L230.35942,344.078261 C230.081158,344.078261 229.578748,344.209661 228.852174,344.472464 C228.1256,344.735267 227.468602,344.990337 226.881159,345.237681 L226,345.655072 L226,347 L241.86087,347 L241.86087,345.655072 Z M229.988406,340.507246 C231.008701,342.64059 232.338156,343.707246 233.976812,343.707246 C235.584549,343.707246 236.898546,342.64059 237.918841,340.507246 C238.289857,340.383574 238.591303,339.997105 238.823188,339.347826 C239.055074,338.698547 239.155556,338.072467 239.124638,337.469565 C239.09372,336.866664 238.954591,336.565217 238.707246,336.565217 C239.047345,335.606758 239.047345,334.802902 238.707246,334.153623 C238.676328,334.060869 238.629952,333.944928 238.568116,333.805797 C238.50628,333.666666 238.343963,333.419325 238.081159,333.063768 C237.818356,332.708211 237.524639,332.391306 237.2,332.113043 C236.875361,331.834781 236.427056,331.579711 235.855072,331.347826 C235.283089,331.115941 234.657008,331 233.976812,331 C232.925599,331 231.998072,331.262799 231.194203,331.788406 C230.390334,332.314012 229.849277,332.839611 229.571014,333.365217 L229.2,334.153623 L229.153623,334.292754 C229.122705,334.35459 229.099517,334.455072 229.084058,334.594203 C229.068599,334.733334 229.05314,334.887922 229.037681,335.057971 C229.022222,335.22802 229.029952,335.444443 229.06087,335.707246 C229.091788,335.97005 229.138164,336.256037 229.2,336.565217 C228.952656,336.565217 228.813527,336.866664 228.782609,337.469565 C228.751691,338.072467 228.852173,338.698547 229.084058,339.347826 C229.315943,339.997105 229.617389,340.383574 229.988406,340.507246 L229.988406,340.507246 Z M230.127536,335.776812 C230.127536,335.684058 230.166183,335.591305 230.243478,335.498551 C230.320773,335.405797 230.552655,335.282126 230.93913,335.127536 C231.325606,334.972946 231.889851,335.05024 232.631884,335.35942 C233.373917,335.637683 234.085021,335.714976 234.765217,335.591304 C235.445414,335.467632 235.971013,335.313044 236.342029,335.127536 C236.713045,334.942028 236.991303,334.756523 237.176812,334.571014 C237.331402,334.571014 237.462801,334.764249 237.571014,335.150725 C237.679228,335.5372 237.733333,335.869564 237.733333,336.147826 L237.733333,337.353623 C237.918842,337.353623 238.042512,337.369082 238.104348,337.4 C238.166184,337.430918 238.197101,337.570047 238.197101,337.817391 C238.197101,338.064736 238.135266,338.435746 238.011594,338.930435 C237.733332,339.456041 237.455074,339.718841 237.176812,339.718841 C237.084058,339.966185 236.952658,340.259902 236.782609,340.6 C236.61256,340.940098 236.257008,341.403862 235.715942,341.991304 C235.174877,342.578747 234.595172,342.872464 233.976812,342.872464 C233.358451,342.872464 232.786476,342.609664 232.26087,342.084058 C231.735263,341.558451 231.364252,341.032853 231.147826,340.507246 L230.823188,339.718841 C230.699516,339.718841 230.560387,339.649276 230.405797,339.510145 C230.251207,339.371014 230.127537,339.239614 230.034783,339.115942 L229.895652,338.930435 C229.61739,337.879222 229.694684,337.353623 230.127536,337.353623 L230.127536,335.776812 Z",
id: "Shape"
}))));
function SvgIconUserNavi(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
width: "16px",
height: "16px",
viewBox: "0 0 16 16",
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink",
ref: svgRef,
"aria-labelledby": titleId
}, props), title === undefined ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, "Avatar") : title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, _ref2, _ref3);
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgIconUserNavi);
export default __webpack_public_path__ + "static/media/icon-user-navi.68643217.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,104 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
var _ref2 = /*#__PURE__*/React.createElement("rect", {
width: 2,
height: 2,
x: 2,
y: 6
});
var _ref3 = /*#__PURE__*/React.createElement("rect", {
width: 2,
height: 2,
x: 5,
y: 6
});
var _ref4 = /*#__PURE__*/React.createElement("rect", {
width: 2,
height: 2,
x: 8,
y: 6
});
var _ref5 = /*#__PURE__*/React.createElement("path", {
d: "m 11,6 3,0 0,5 -2,0 0,-3 -1,0 z"
});
var _ref6 = /*#__PURE__*/React.createElement("rect", {
width: 2,
height: 2,
x: 12,
y: 12
});
var _ref7 = /*#__PURE__*/React.createElement("rect", {
width: 6,
height: 2,
x: 5,
y: 12
});
var _ref8 = /*#__PURE__*/React.createElement("rect", {
width: 2,
height: 2,
x: 9,
y: 9
});
var _ref9 = /*#__PURE__*/React.createElement("rect", {
width: 2,
height: 2,
x: 6,
y: 9
});
var _ref10 = /*#__PURE__*/React.createElement("rect", {
width: 2,
height: 2,
x: 2,
y: 12
});
var _ref11 = /*#__PURE__*/React.createElement("rect", {
width: 3,
height: 2,
x: 2,
y: 9
});
function SvgKeyboard(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
width: 16,
height: 16,
style: {
fill: "#888"
},
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("path", {
style: {
fill: "none",
stroke: "#888"
},
d: "M 0.5,4.5 15.5,4.5 15.5,15.5 0.5,15.5 Z"
}), _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11));
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgKeyboard);
export default __webpack_public_path__ + "static/media/keyboard.3a060932.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/nav-cross-black-lg.4c2e3c0d.svg";

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/nav-cross-black-md.f5825c8f.svg";

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/nav-cross-lg.a759f43e.svg";

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/nav-cross-xs.fec778ad.svg";

View File

@ -0,0 +1,66 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
function SvgSmileyNegative(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
width: "100%",
height: "100%",
viewBox: "0 0 55 54",
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink",
xmlSpace: "preserve",
style: {
fillRule: "evenodd",
clipRule: "evenodd",
strokeLinejoin: "round",
strokeMiterlimit: 1.41421
},
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, /*#__PURE__*/React.createElement("circle", {
className: "dot",
cx: 27.5,
cy: 27,
r: 27,
style: {
fill: "#A9A094"
}
}), /*#__PURE__*/React.createElement("g", {
id: "face"
}, /*#__PURE__*/React.createElement("circle", {
cx: 18.5,
cy: 18.5,
r: 3.5,
style: {
fill: "#fff"
}
}), /*#__PURE__*/React.createElement("circle", {
cx: 36.5,
cy: 18.5,
r: 3.5,
style: {
fill: "#fff"
}
}), /*#__PURE__*/React.createElement("path", {
d: "M40.902,32.312l-26.41,5.613l0.624,2.935l26.41,-5.614l-0.624,-2.934Z",
style: {
fill: "#fff"
}
})));
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgSmileyNegative);
export default __webpack_public_path__ + "static/media/smiley-negative.6c289ab6.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,57 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
function SvgSmileyPositive(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
width: "100%",
height: "100%",
viewBox: "0 0 55 54",
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink",
xmlSpace: "preserve",
style: {
fillRule: "evenodd",
clipRule: "evenodd",
strokeLinejoin: "round",
strokeMiterlimit: 1.41421
},
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, /*#__PURE__*/React.createElement("circle", {
className: "dot",
cx: 27.5,
cy: 27,
r: 27,
style: {
fill: "#A9A094"
}
}), /*#__PURE__*/React.createElement("g", {
id: "face"
}, /*#__PURE__*/React.createElement("path", {
d: "M26.439,37.092l-11.313,-11.314l-2.829,2.829l11.314,11.313l2.828,-2.828Z",
style: {
fill: "#fff"
}
}), /*#__PURE__*/React.createElement("path", {
d: "M39.874,18l-19.092,19.092l2.829,2.828l19.092,-19.092l-2.829,-2.828Z",
style: {
fill: "#fff"
}
})));
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgSmileyPositive);
export default __webpack_public_path__ + "static/media/smiley-positive.3d91069e.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/star-gray.84ad33dd.svg";

View File

@ -0,0 +1,2 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
export default __webpack_public_path__ + "static/media/star-white.d32dc96a.svg";

View File

@ -0,0 +1,41 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
var _ref2 = /*#__PURE__*/React.createElement("g", {
fill: "none",
fillRule: "evenodd"
}, /*#__PURE__*/React.createElement("path", {
d: "M-2.5-2.5h17v17h-17z"
}), /*#__PURE__*/React.createElement("path", {
fill: "#FFF",
d: "M12.906 1.219C7.434 4.9 5.47 10.25 5.47 10.25H4.406S2.581 7.472.688 6.531l.53-1.062c2.089.469 3.72 1.593 3.72 1.593s1.78-3.61 7.437-6.906l.531 1.063z"
}), /*#__PURE__*/React.createElement("path", {
d: "M-4 16h20V-4H-4z"
}));
function SvgTick(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
width: 13,
height: 11,
viewBox: "0 0 13 11",
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, _ref2);
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgTick);
export default __webpack_public_path__ + "static/media/tick.adf0c598.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,37 @@
const __webpack_public_path__ = "https://learngerman.dw.com/";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import * as React from "react";
var _ref2 = /*#__PURE__*/React.createElement("g", {
className: "arrowBox",
fillRule: "evenodd"
}, /*#__PURE__*/React.createElement("g", {
className: "arrowArrow",
transform: "translate(12, 12), rotate(0 0 0) scale(1.0)"
}, /*#__PURE__*/React.createElement("polygon", {
points: "12 -4.7125 0 4.9375 -12 -4.6875 -10.375 -5.5 0 0.125 10.375 -5.5"
})));
function SvgToggleableArrow(_ref, svgRef) {
var title = _ref.title,
titleId = _ref.titleId,
props = _objectWithoutProperties(_ref, ["title", "titleId"]);
return /*#__PURE__*/React.createElement("svg", _extends({
viewBox: "0 0 24 24",
xmlns: "http://www.w3.org/2000/svg",
ref: svgRef,
"aria-labelledby": titleId
}, props), title ? /*#__PURE__*/React.createElement("title", {
id: titleId
}, title) : null, _ref2);
}
var ForwardRef = /*#__PURE__*/React.forwardRef(SvgToggleableArrow);
export default __webpack_public_path__ + "static/media/toggleableArrow.a17a06a6.svg";
export { ForwardRef as ReactComponent };

View File

@ -0,0 +1,23 @@
import styled from 'styled-components';
import { StyledAccordionItem as AccordionItem } from './AccordionItem/AccordionItem';
import { grammarOverviewColors } from '../../constants/grammarOverviewColors';
export const Accordion = ({ className, data }) => {
return (
<div className={className}>
{data.map((accordionData, index) => (
<AccordionItem
key={accordionData.id}
backgroundColor={grammarOverviewColors[index % grammarOverviewColors.length]}
title={accordionData.headline}
contents={accordionData.grammars}
className={className}
/>
))}
</div>
);
};
export const StyledAccordion = styled(Accordion)`
display: block;
`;

View File

@ -0,0 +1,111 @@
import { useRef } from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { useToggle } from '../../../hooks/useToggle';
import { colors, resolutions } from '../../../utils/css';
import { StyledToggleableArrow as ToggleableArrow } from '../../ToggleableArrow/ToggleableArrow';
import { I18nText } from '../../I18n/I18nText';
import { getContentUrl } from '../../../utils/url/urlFactory';
export const AccordionItem = ({ title, contents, className }) => {
const element = useRef(null);
const [isOpen, { toggleOnClick }] = useToggle();
const height = element.current ? element.current.scrollHeight : '0';
return (
<div className={className}>
<button tabIndex={0} className="accordionItem-btn" onClick={toggleOnClick}>
<I18nText translation={title} isA="p" className={className} />
<ToggleableArrow
className="toggleable-arrow"
fill={colors.LG_WHITE}
{...{
isUp: isOpen,
}}
/>
</button>
<AccordionItemContent height={height} isOpen={isOpen} ref={element} aria-expanded={isOpen}>
{contents.map(content => (
<div key={content.id} className="accordionItem-text">
<Link
tabIndex={isOpen ? 0 : -1}
to={getContentUrl({
id: content.id,
name: content.shortTitle,
language: content.language,
__typename: content.knowledgeType,
})}
>
{content.shortTitle}
</Link>
</div>
))}
</AccordionItemContent>
</div>
);
};
export const StyledAccordionItem = styled(AccordionItem)`
font-family: DWTheAntiquaB, Georgia, serif;
font-weight: 400;
font-size: 18px;
line-height: 25px;
text-align: left;
display: flex;
flex-direction: column;
margin: 0px;
.accordionItem-btn {
display: flex;
position: relative;
width: 100%;
padding: 20px;
background-color: ${({ backgroundColor }) => backgroundColor};
color: ${colors.LG_WHITE};
cursor: pointer;
align-items: center;
border: none;
:hover {
background-color: ${colors.LG_GRAY_9};
}
:focus {
color: ${colors.LG_BAHAMA_BLUE};
text-decoration: underline;
}
}
.toggleable-arrow {
right: 18px;
}
`;
export const AccordionItemContent = styled.div`
max-height: ${({ isOpen, height }) => (isOpen ? height : '1')}px;
overflow: hidden;
background-color: ${colors.LG_WHITE};
transition: max-height 0.7s;
.accordionItem-text {
font-size: 18px;
line-height: 25px;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: ${colors.LG_GRAY_12};
a {
color: ${colors.LG_GRAY_9};
text-decoration: none;
padding: 20px;
display: block;
&:hover {
color: ${colors.LG_WHITE};
background-color: ${colors.LG_GRAY_9};
}
}
@media (max-width: ${resolutions.max.mobile}px) {
font-size: 15px;
line-height: 20px;
}
}
`;

View File

@ -0,0 +1,214 @@
import { Redirect, Route, Switch } from 'react-router-dom';
import { useParams } from 'react-router';
import { Default404Page } from '../pages/Default404Page';
import { urlPrefixToType } from '../utils/url/url';
import { CoursePage } from '../pages/CoursePage';
import { GrammarPage } from '../pages/GrammarPage';
import { DashboardPage } from '../pages/DashboardPage';
import { GrammarOverviewPage } from '../pages/GrammarOverviewPage';
import { pathPartials } from '../utils/url/pathAnalyser';
import { LessonPage } from '../pages/LessonPage';
import {
PAGE_GRAMMAR,
PAGE_LEGAL,
PAGE_OVERVIEW,
PAGE_PASSWORD_CHANGE,
PAGE_PASSWORD_RESET,
PAGE_PLACEMENT_TEST,
PAGE_PROFILE,
PAGE_REGISTRATION,
PAGE_VOCABULARY,
PAGE_VOCABULARY_TRAINER,
PAGE_VOCABULARY_TRAINER_START,
STATUS_FEEDBACK,
} from '../utils/url/urlFactory';
import { FEEDBACK_TYPES } from '../constants/feedback';
import { RestrictedRoute } from './RestrictedRoute/RestrictedRoute';
import { ConfirmUserRegistrationContainer } from './user/ConfirmUserRegistrationContainer';
import { Registration } from './user/Registration';
import { PasswordChange } from './user/PasswordChange';
import { PasswordReset } from './user/PasswordReset';
import { StatusFeedback } from './StatusFeedback/StatusFeedback';
import { PageContainer, withPageContainerAndMetadata } from './Page/PageContainer';
import { UserProfile } from './user/UserProfile';
import { SetNewPassword } from './user/SetNewPassword';
import { PlacementTestPage } from '../pages/PlacementTestPage';
import { VocabularyTrainer } from './VocabularyTrainer/VocabularyTrainer';
import { PlacementAndFinalTestResultPage } from '../pages/PlacementAndFinalTestResultPage';
import { HelpPage } from '../pages/HelpPage';
import { GTM_NO_CONTENT_PAGE_IDS, PAGE_TYPES } from '../constants/pageTypes';
import { PageMetaData } from './Page/PageMetaData';
import { LegalPage } from '../pages/LegalPage';
import { VocabularyPage } from '../pages/VocabularyPage';
import { GtmScriptWithDataLayer } from './GoogleTagManager';
const typeToPage = {
COURSE: {
type: 'Course',
component: CoursePage,
},
GRAMMAR: {
type: 'Grammar',
component: GrammarPage,
},
LESSON: {
type: 'Lesson',
component: LessonPage,
},
PLACEMENT: {
type: 'Placement',
component: PlacementAndFinalTestResultPage,
},
FINAL: {
type: 'Final',
component: PlacementAndFinalTestResultPage,
},
};
export const ContentPage = () => {
const params = useParams();
const pageDef = typeToPage[urlPrefixToType(params.typeId)];
const Tag = pageDef.component;
return <Tag {...params} type={pageDef.type} />;
};
export const App = () => (
<Switch>
<Route
exact
path="/"
render={() => (
<Redirect
to={{
pathname: '/en/overview',
}}
/>
)}
/>
<Route
exact
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_OVERVIEW}`}
component={DashboardPage}
/>
<Route
path={`/:langCode(${pathPartials.langCodeRegex})/:title(${pathPartials.titlePathRegex})?/l-:lessonId(${pathPartials.contentIdRegex})`}
component={LessonPage}
/>
<Route
path={`/:langCode(${pathPartials.langCodeRegex})/:title(${pathPartials.titlePathRegex})?/:typeId(${pathPartials.contentTypeRegex})-:contentId(${pathPartials.contentIdRegex})`}
component={ContentPage}
/>
<Route
exact
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_GRAMMAR}`}
component={GrammarOverviewPage}
/>
<Route exact path={`/:langCode(${pathPartials.langCodeRegex})/help`} component={HelpPage} />
<Route
exact
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_LEGAL}`}
component={LegalPage}
/>
<Route
exact
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_VOCABULARY}`}
component={VocabularyPage}
/>
<Route
exact
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_REGISTRATION}`}
render={() =>
withPageContainerAndMetadata({
component: Registration,
pageType: PAGE_TYPES.REGISTER_USER,
useDescriptionTranslation: true,
})
}
/>
<Route
path={`/:langCode(${pathPartials.langCodeRegex})/user/password/set`}
render={({ location }) => (
<PageContainer>
<PageMetaData pageType={PAGE_TYPES.PASSWORD_SET} addKeywordsAndDescription={false} />
<GtmScriptWithDataLayer noContentPageId={GTM_NO_CONTENT_PAGE_IDS.PASSWORD_SET} />
<SetNewPassword
confirmationId={new URLSearchParams(location.search).get('confirmationId')}
/>
</PageContainer>
)}
/>
<Route
path={`/:langCode(${pathPartials.langCodeRegex})/user/register/confirm`}
render={({ location }) => (
<ConfirmUserRegistrationContainer
confirmationId={new URLSearchParams(location.search).get('confirmationId')}
/>
)}
/>
<RestrictedRoute
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_PROFILE}`}
render={() =>
withPageContainerAndMetadata({
component: UserProfile,
pageType: PAGE_TYPES.USER_PROFILE,
addDescriptionAndKeywordsMeta: false,
})
}
feedbackType={FEEDBACK_TYPES.MISSING_USER_PROFILE}
/>
<RestrictedRoute
exact
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_PASSWORD_CHANGE}`}
render={() =>
withPageContainerAndMetadata({
component: PasswordChange,
pageType: PAGE_TYPES.PASSWORD_CHANGE,
addDescriptionAndKeywordsMeta: false,
})
}
/>
<RestrictedRoute
exact
path={[
`/:langCode(${pathPartials.langCodeRegex})${PAGE_VOCABULARY_TRAINER}`,
`/:langCode(${pathPartials.langCodeRegex})${PAGE_VOCABULARY_TRAINER_START}`,
]}
render={() =>
withPageContainerAndMetadata({
component: VocabularyTrainer,
pageType: PAGE_TYPES.VOCABULARY_TRAINER,
addDescriptionAndKeywordsMeta: false,
})
}
/>
<Route
exact
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_PASSWORD_RESET}`}
render={() =>
withPageContainerAndMetadata({
component: PasswordReset,
pageType: PAGE_TYPES.PASSWORD_RESET,
addDescriptionAndKeywordsMeta: false,
})
}
/>
<Route
exact
path={`/:langCode(${pathPartials.langCodeRegex})${STATUS_FEEDBACK}/:feedbackType(${pathPartials.feedbackTypes})`}
render={() =>
withPageContainerAndMetadata({
component: StatusFeedback,
pageType: PAGE_TYPES.FEEDBACK_STATUS,
addDescriptionAndKeywordsMeta: false,
})
}
/>
<Route
exact
path={`/:langCode(${pathPartials.langCodeRegex})${PAGE_PLACEMENT_TEST}`}
component={PlacementTestPage}
/>
<Route exact path={`/:langCode(${pathPartials.langCodeRegex})`} component={Default404Page} />
<Route component={Default404Page} />
</Switch>
);

View File

@ -0,0 +1,177 @@
import styled from 'styled-components';
import { colors, resolutions } from '../../utils/css';
import navCrossBlackMd from '../../assets/svg/nav-cross-black-md.svg';
import navCrossBlackLg from '../../assets/svg/nav-cross-black-lg.svg';
import navCrossXs from '../../assets/svg/nav-cross-xs.svg';
import navCrossLg from '../../assets/svg/nav-cross-lg.svg';
import { StyledIconBar as IconBar } from '../IconBar/IconBar';
export const BurgerButton = ({
isNavMenuOpen,
toggleIsNavMenuOpen,
setIsLanguageSubMenuOpen,
className,
}) => (
<button
onClick={() => {
toggleIsNavMenuOpen();
setIsLanguageSubMenuOpen(false);
}}
type="button"
className={className}
data-toggle="collapse"
data-target="#nav-user-menu"
aria-haspopup="true"
aria-controls="navbar"
aria-expanded={isNavMenuOpen}
>
{isNavMenuOpen && <span className="close-menu" />}
<span className="screen-reader-only">Toggle navigation</span>
{!isNavMenuOpen && (
<>
<IconBar />
<IconBar />
<IconBar />
</>
)}
</button>
);
const defaultHoverStyles = `
&:hover {
.close-menu {
background: url(${navCrossXs});
background-size: cover;
}
}
&:hover {
${IconBar} {
background-color: ${colors.LG_BLACK};
}
}
`;
/*
CSS parsed by a string helper function to return css string with styling blocks as arguments,
could not be tested with jest-styled-components.
It reads the media option correctly (media: (hover:hover)). But it throws an unknown error.
While using string templates passes with known media option.
*/
const mediaHoverDeviceDetection = `
@media (hover: hover) and (pointer: fine) {
${defaultHoverStyles}
}
@media (hover: none) and (pointer: coarse) {
${defaultHoverStyles}
}
`;
const closeMenuMobile = `
height: 12px;
width: 13px;
`;
const closeMenuTablet = `
height: 21px;
width: 22px;
`;
export const StyledBurgerButton = styled(BurgerButton)`
z-index: 3000;
display: block;
position: fixed;
background-color: transparent;
background-image: none;
padding: 0;
/*! @noflip */
right: 20px;
top: 13px;
border: none;
border-radius: 0;
-webkit-appearance: button;
cursor: pointer;
@media (max-width: ${resolutions.max.mobile}px) {
top: 20px;
&:hover {
.close-menu {
${closeMenuMobile}
}
}
}
@media (min-width: ${resolutions.min.tablet}px) and (max-width: ${resolutions.max.tablet}px) {
top: 28px;
&:hover {
.close-menu {
${closeMenuTablet}
}
}
}
@media (min-width: ${resolutions.min.desktop}px) {
margin-top: 32px;
z-index: 1;
position: absolute;
top: 0;
/*! @noflip */
right: 30px;
}
${mediaHoverDeviceDetection}
&:focus {
outline: 0;
.close-menu {
background: url(${navCrossBlackLg});
background-size: cover;
}
}
.close-menu {
background: url(${navCrossLg});
display: block;
height: 21px;
margin-top: 5px;
/*! @noflip */
right: 15px;
width: 22px;
@media (max-width: ${resolutions.min.desktop}px) {
background: url(${navCrossXs});
height: 12px;
width: 13px;
}
@media (min-width: ${resolutions.min.tablet}px) and (max-width: ${resolutions.max.tablet}px) {
background: url(${navCrossBlackMd});
height: 21px;
margin-top: 5px;
width: 22px;
}
@media (min-width: ${resolutions.min.desktop}px) {
background: url(${navCrossBlackMd});
height: 21px;
margin-top: 0px;
position: absolute;
/*! @noflip */
right: 0px;
width: 22px;
}
}
.screen-reader-only {
border: none;
clip: rect(0, 0, 0, 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
`;

View File

@ -0,0 +1,44 @@
import { hasErrorClassAssigner } from '../../utils/errorClassAssigner';
import { getTranslatedValidationMessage } from '../../utils/validation/validationMessages';
import { useI18nContext } from '../../context/I18nContext';
export const Checkbox = ({
name,
labelText,
validationErrorTranslationKey,
register,
validationError,
...restProps
}) => {
const { i18n } = useI18nContext();
return (
<div className="custom-checkbox">
<div className={hasErrorClassAssigner('form-group', validationError)}>
<div className="input-group">
<span className="input-group-addon">
<input
className="form-control"
name={name}
type="checkbox"
aria-label={name}
{...register}
{...restProps}
/>
</span>
<label className="checkbox-label">{labelText}</label>
</div>
{validationError && (
<small className="help-block">
{getTranslatedValidationMessage({
name,
validationErrorTranslationKey,
validationType: validationError.type,
t: i18n.t,
})}
</small>
)}
</div>
</div>
);
};

View File

@ -0,0 +1,53 @@
import styled from 'styled-components';
import { useRef } from 'react';
import { useToggle } from '../../hooks/useToggle';
import { StyledToggleableArrow as ToggleableArrow } from '../ToggleableArrow/ToggleableArrow';
import { colors } from '../../utils/css';
import { useTranslation } from '../../hooks/useTranslation';
export const AccordionContainer = ({ title, children, className }) => {
const titleTranslation = useTranslation(title);
return (
<details className={className}>
<summary className="row noVMargins">
<div className="col-sm-offset-1 col-sm-10 col-lg-offset-2 col-lg-8">
<h4>
{titleTranslation}
</h4>
</div>
</summary>
<AccordionContainerContent
>
{children}
</AccordionContainerContent>
</details>
);
};
export const AccordionContainerContent = styled.div`
overflow: hidden;
background-color: ${colors.LG_WHITE};
transition: max-height 0.7s;
margin-bottom: 20px;
`;
export const StyledAccordionContainer = styled(AccordionContainer)`
button {
outline: none;
padding: 0;
display: flex;
position: relative;
cursor: pointer;
border: none;
background-color: ${colors.LG_WHITE};
width: 10em;
}
.toggleable-arrow {
position: relative;
top: 0.1em;
margin-left: 0.2em;
width: 20px;
height: 17px;
}
`;

View File

@ -0,0 +1,70 @@
import styled from 'styled-components';
import { useRef } from 'react';
import { useToggle } from '../../hooks/useToggle';
import { StyledToggleableArrow as ToggleableArrow } from '../ToggleableArrow/ToggleableArrow';
import { colors } from '../../utils/css';
import { useTranslation } from '../../hooks/useTranslation';
export const AccordionContainer = ({ title, children, className }) => {
const element = useRef(null);
const [isOpen, { toggleOnClick }] = useToggle();
const height = element.current ? element.current.scrollHeight : '0';
const titleTranslation = useTranslation(title);
return (
<div className={className}>
<div className="row noVMargins">
<div className="col-sm-offset-1 col-sm-10 col-lg-offset-2 col-lg-8">
<button tabIndex={0} onClick={toggleOnClick}>
<h4>
{titleTranslation}
<ToggleableArrow
className="toggleable-arrow"
fill={colors.LG_BLACK}
{...{
isUp: isOpen,
}}
/>
</h4>
</button>
</div>
</div>
<AccordionContainerContent
height={height}
isOpen={isOpen}
ref={element}
aria-expanded={isOpen}
>
{children}
</AccordionContainerContent>
</div>
);
};
export const AccordionContainerContent = styled.div`
max-height: ${({ isOpen, height }) => (isOpen ? height : '1')}px;
overflow: hidden;
background-color: ${colors.LG_WHITE};
transition: max-height 0.7s;
margin-bottom: 20px;
`;
export const StyledAccordionContainer = styled(AccordionContainer)`
button {
outline: none;
padding: 0;
display: flex;
position: relative;
cursor: pointer;
border: none;
background-color: ${colors.LG_WHITE};
width: 10em;
}
.toggleable-arrow {
position: relative;
top: 0.1em;
margin-left: 0.2em;
width: 20px;
height: 17px;
}
`;

View File

@ -0,0 +1,5 @@
export const ContentBottomLine = () => (
<div className="row vocabulary list-end">
<div className="col-sm-offset-1 col-sm-10 col-lg-offset-2 col-lg-8 vocabulary-entry" />
</div>
);

View File

@ -0,0 +1,9 @@
import { ContentNavTitle } from './ContentNavTitle/ContentNavTitle';
export const ContentContainer = ({ title, children }) => (
<div className="section exercise-container vocabulary copy">
<ContentNavTitle {...{ title, top: true }} />
{children}
<ContentNavTitle {...{ title, bottom: true }} />
</div>
);

View File

@ -0,0 +1,13 @@
import classnames from 'classnames';
export const ContentHeadline = ({ children, isStandalone = false }) => {
return (
<div className="headline-container row">
<div className="col-sm-offset-1 col-sm-10 col-lg-offset-2 col-lg-8 ">
<div className={classnames('exercise-headline', { standalone: isStandalone })}>
{children}
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,22 @@
import classnames from 'classnames';
import { I18nText } from '../../I18n/I18nText';
export const ContentNavTitle = ({ title, top, bottom }) => (
<div className="row noVMargins">
<div className="col-sm-offset-1 col-sm-10 col-lg-offset-2 col-lg-8">
<div
className={classnames('exercise-nav', {
top,
bottom,
})}
>
<I18nText
isA="div"
className="exercise-nav-title"
style={{ direction: 'ltr' }}
translation={title}
/>
</div>
</div>
</div>
);

View File

@ -0,0 +1,16 @@
import { I18nText } from '../../I18n/I18nText';
export const ContentSection = ({ title, children }) => (
<>
<div className="row vocabulary">
<div className="col-sm-offset-1 col-sm-10 col-lg-offset-2 col-lg-8">
<I18nText isA="h3" translation={title} />
</div>
</div>
<div className="row vocabulary">
<div className="col-sm-offset-1 col-sm-10 col-lg-offset-2 col-lg-8">
<div className="richtext-content-container">{children}</div>
</div>
</div>
</>
);

View File

@ -0,0 +1,69 @@
import styled from 'styled-components';
import { StyledHeroTemplate as HeroTemplate } from '../../HeroTemplate/HeroTemplate';
import { resolutions } from '../../../utils/css';
const determineImage = ({ imageId, imageBasePath }) => {
return `${imageBasePath}/${imageId}`;
};
export const StyledCourseHeroImage = styled(HeroTemplate)`
background: url(${determineImage}_509.jpg) center center / cover no-repeat;
height: 320px;
.content {
width: 100%;
padding: 0 20px;
}
.title {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.identifier {
text-transform: uppercase;
font: 700 40px Helvetica, Arial, sans-serif;
opacity: 0.5;
}
.description {
/* @noflip */
direction: ltr;
font: 400 40px DWTheAntiquaB, Georgia, serif;
opacity: 1;
margin: 0 8px;
}
@media (max-width: ${resolutions.max.tablet}px) {
.identifier,
.description {
font-size: 36px;
}
}
@media (max-width: 992px) {
background: url(${determineImage}_507.jpg) center center / cover no-repeat;
.identifier,
.description {
font-size: 32px;
}
}
@media (max-width: ${resolutions.max.mobile}px) {
.identifier,
.description {
font-size: 28px;
}
}
@media (max-width: 480px) {
background: url(${determineImage}_503.jpg) center center / cover no-repeat;
}
@media (max-width: 360px) {
background: url(${determineImage}_501.jpg) center center / cover no-repeat;
}
`;

View File

@ -0,0 +1,30 @@
import { resolutions } from '../../../../utils/css';
import { useToggle } from '../../../../hooks/useToggle';
import { StyledCourseHeader as CourseHeader } from '../CourseHeader/CourseHeader';
import { StyledLessonList as LessonList } from '../LessonList/LessonList';
import globals from '../../../../utils/globals';
export const CourseAccordion = ({ groupNameTranslationKey, groupedLessons, onClickReset }) => {
const isOnDesktop = globals.window.innerWidth >= resolutions.min.desktop;
const [isOpen, { toggleOnClick, toggleOnKeyPress }] = useToggle({ initialState: isOnDesktop });
return (
<div className="accordion" aria-label="course.accordion">
<CourseHeader
{...{
isOpen,
groupNameTranslationKey,
onClick: toggleOnClick,
onKeyPress: toggleOnKeyPress,
}}
/>
<LessonList
{...{
isOpen,
groupedLessons,
onClickReset,
}}
/>
</div>
);
};

View File

@ -0,0 +1,78 @@
import styled from 'styled-components';
import classnames from 'classnames';
import { I18nText } from '../../../I18n/I18nText';
import { colors, resolutions } from '../../../../utils/css';
import { StyledToggleableArrow as ToggleableArrow } from '../../../ToggleableArrow/ToggleableArrow';
const CourseHeader = ({ className, groupNameTranslationKey, isOpen, onClick, onKeyPress }) => {
return (
<div
className={classnames(className, 'header')}
role="button"
tabIndex="0"
onClick={onClick}
onKeyPress={onKeyPress}
aria-label="course.header"
>
<I18nText
className={classnames('title')}
{...{
isA: 'h2',
}}
aria-label="course.groupName"
translation={groupNameTranslationKey || 'course.otherLessons'}
/>
<ToggleableArrow
className="toggleable-arrow"
fill={colors.LG_GRAY_13}
{...{
isUp: isOpen,
}}
/>
</div>
);
};
export const StyledCourseHeader = styled(CourseHeader)`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
border-top: 1px solid ${colors.LG_GRAY_TRANSPARENT_1};
height: 163px;
pointer-events: none;
.title {
height: 25px;
background-color: ${colors.LG_WHITE};
color: ${colors.LG_GRAY_13};
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
font-family: Helvetica, Arial, sans-serif;
font-weight: 700;
font-size: 22px;
line-height: 23px;
margin: 0;
padding: 0 20px;
}
.toggleable-arrow {
display: none;
}
@media (max-width: ${resolutions.max.tablet}px) {
height: 94px;
pointer-events: all;
.title {
padding: 0 45px 0 20px;
border-top: 0;
}
.toggleable-arrow {
display: inline-block;
right: 40px;
}
}
`;

View File

@ -0,0 +1,48 @@
import styled from 'styled-components';
import { colors, resolutions } from '../../../utils/css';
import { CourseAccordion } from './CourseAccordion/CourseAccordion';
export const CourseList = ({ className, groupedLessons, onClickResetLesson }) => (
<ul className={className} aria-label="course.list">
{Object.keys(groupedLessons).map(key => (
// group lessons by their group names
<li key={groupedLessons[key][0].id} className="course">
<CourseAccordion
{...{
groupNameTranslationKey: groupedLessons[key][0].groupName,
groupedLessons: groupedLessons[key],
onClickReset: onClickResetLesson,
}}
/>
</li>
))}
</ul>
);
export const StyledCourseList = styled(CourseList)`
* {
box-sizing: border-box;
}
display: grid;
grid-template-columns: repeat(4, 1fr);
width: 100%;
list-style-type: none;
margin: 0;
padding: 0;
.course {
display: inline-block;
background: ${colors.LG_WHITE};
&:nth-child(odd) {
.accordion .lesson-list {
background-color: ${colors.LG_GRAY_3};
}
}
}
@media (max-width: ${resolutions.max.tablet}px) {
grid-template-columns: 100%;
}
`;

View File

@ -0,0 +1,120 @@
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { StyledText as Text } from '../../../Text/Text';
import { colors } from '../../../../utils/css';
import { getContentUrl } from '../../../../utils/url/urlFactory';
import { StyledLessonScore as LessonScore } from './LessonScore';
import { StyledSmallProgressResetButton as ProgressResetButton } from '../../Progress/ProgressResetButton';
import { LearnProgressIcon } from '../../../LearnProgressIcon/LearnProgressIcon';
import { findLessonLearnProgressSelector } from '../../../../state/progress/learnProgressSelectors';
export const LessonItem = ({ lesson, className, onClickReset }) => {
const lessonId = lesson.id;
const lessonProgress = useSelector(findLessonLearnProgressSelector(lessonId));
const onHandleResetClick = e => {
onClickReset(e, lessonId);
};
return (
<>
<Link
className={className}
to={getContentUrl({
id: lesson.id,
name: lesson.shortTitle,
language: lesson.language,
__typename: 'Lesson',
})}
tabIndex={0}
>
<Text className="title" isA="h3" aria-label="lesson.title">
{lesson.shortTitle}
</Text>
<div className={`tile-progress`}>
<LearnProgressIcon learnProgress={lessonProgress} />
</div>
<Text className="learningTarget" aria-label="lesson.learningTarget">
{lesson.learningTargetHeadline}
</Text>
{lessonProgress && (
<>
<LessonScore
className="lessonScore"
lessonProgress={lessonProgress}
onClickReset={onClickReset}
lessonId={lesson.id}
/>
<ProgressResetButton className="resetButton" onClickReset={onHandleResetClick} />
</>
)}
<Text className="categories" aria-label="lesson.categories">
{lesson.grammarDescription}
</Text>
</Link>
</>
);
};
const alignProgressData = '5px';
export const StyledLessonItem = styled(LessonItem)`
border-top: 1px solid ${colors.LG_GRAY_TRANSPARENT_1};
color: ${colors.LG_GRAY_8};
display: grid;
font-size: 14px;
grid-template-columns: 1fr auto;
grid-template-rows: repeat(3, 1fr);
height: 102px;
padding: 10px 20px;
overflow-y: hidden;
text-decoration: none;
&:hover {
color: ${colors.LG_GRAY_8};
text-decoration: none;
}
.title {
color: ${colors.LG_GRAY_9};
font-family: DWTheAntiquaBBold, Georgia-Bold, serif;
font-size: 17px;
line-height: 24px;
margin: 0;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.learningTarget,
.categories {
font-family: DWTheAntiquaB, Georgia, serif;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.categories {
grid-column: 1 / 2;
}
.tile-progress {
display: flex;
justify-content: flex-end;
padding: 5px 0 0 0;
padding-right: ${alignProgressData};
}
.lessonScore {
grid-column-start: 2;
justify-self: flex-end;
padding-right: ${alignProgressData};
}
.resetButton {
grid-column-start: 2;
grid-row-start: 3;
justify-self: center;
}
`;

View File

@ -0,0 +1,41 @@
import styled from 'styled-components';
import { colors } from '../../../../utils/css';
import { calculatePercentage } from '../../../../utils/commons';
export const LessonScore = ({ className, lessonProgress }) => {
const { resultPoints, maxPointsForAllExercises } = lessonProgress;
const resultPointsClassName = () => {
const resultInPercentage = calculatePercentage(resultPoints, maxPointsForAllExercises);
if (resultInPercentage >= 70) {
return 'good';
}
if (resultInPercentage <= 40) {
return 'low';
}
return 'mid';
};
return (
<div className={className}>
<span className={resultPointsClassName()}>{resultPoints}</span>
<span>/</span>
<span>{maxPointsForAllExercises}</span>
</div>
);
};
export const StyledLessonScore = styled(LessonScore)`
font-size: 14px;
.low {
color: ${colors.LG_RED_SCORE};
}
.mid {
color: ${colors.LG_GRAY_11};
}
.good {
color: ${colors.LG_GREEN_SCORE};
}
`;

View File

@ -0,0 +1,47 @@
import styled from 'styled-components';
import classnames from 'classnames';
import { colors, resolutions } from '../../../../utils/css';
import { StyledLessonItem as LessonItem } from '../LessionItem/LessonItem';
export const LessonList = ({ className, groupedLessons, onClickReset, isOpen = true }) => (
// eslint-disable-next-line jsx-a11y/role-supports-aria-props
<ul
className={classnames(className, 'lesson-list', { collapsed: !isOpen })}
aria-expanded={isOpen}
aria-label="lesson.list"
>
{groupedLessons.map(lesson => (
<li key={lesson.id} className="lesson-item">
<LessonItem
{...{
lesson: lesson.target,
onClickReset,
}}
/>
</li>
))}
</ul>
);
export const StyledLessonList = styled(LessonList)`
transition: max-height 0.3s linear;
overflow-y: hidden;
visibility: visible;
max-height: ${props => props.groupedLessons.length * 102}px;
list-style-type: none;
margin: 0;
padding: 0;
@media (max-width: ${resolutions.max.tablet}px) {
background-color: ${colors.LG_GRAY_3};
&:last-child {
border-bottom: 0;
}
&.collapsed {
visibility: hidden;
max-height: 0;
}
}
`;

View File

@ -0,0 +1,140 @@
import { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { createGlobalStyle } from 'styled-components';
import {
groupBy,
isFinalTestPredicate,
removeContentLinksWithEmptyTarget,
} from '../../utils/commons';
import { useFrontendConfig } from '../../hooks/useFrontendConfig';
import {
deleteCourseProgress,
deleteLessonProgress,
initCourse,
readCourseProgress,
readLessonProgresses,
} from '../../state/progress/learnProgressDuck';
import { isMonolingualCourse } from '../../constants/isLtrContentOnly';
import { StyledCourseHeroImage as CoursePageHeroImage } from './CourseHeroImage/CourseHeroImage';
import { I18nText } from '../I18n/I18nText';
import { StyledCourseList as CourseList } from './CourseList/CourseList';
import { ModalIntegration } from '../Modal/ModalIntegration';
import { ResetModal } from '../Modal/ResetModal';
import { StyledCourseProgress as CourseProgress } from './Progress/CourseProgress';
import { learningLevelMapper } from '../../utils/learningLevelUtils';
export const LTRContentCourseOverviewStyle = createGlobalStyle`
#courses {
.title,
span[aria-label*="lesson"] {
/* @noflip */
direction: ltr;
}
.content .title {
/* @noflip */
text-align: left;
}
}
`;
export const CourseOverview = ({ data, contentId }) => {
const isMonoLingualCourse = isMonolingualCourse(contentId);
const {
name,
dkLearningLevel,
contentLinks: lessonContentLinks,
mainContentImageLink,
learningMetaInfo: { achievableScore, exerciseCount } = {},
} = data.content;
const filteredContentLinks = removeContentLinksWithEmptyTarget(lessonContentLinks);
const finalLessonId = filteredContentLinks
.filter(contentLink => isFinalTestPredicate(contentLink))
.map(contentLink => contentLink.target.id);
const allLessonIds = filteredContentLinks.map(contentLink => contentLink.target.id);
const groupedLessons = groupBy('groupName', filteredContentLinks);
const { imageBasePath } = useFrontendConfig();
const [isResetModalOpen, setResetModalOpen] = useState(false);
const [lessonIdToDelete, setLessonIdToDelete] = useState(null);
const dispatch = useDispatch();
useEffect(() => {
dispatch(
initCourse({
allExerciseCount: exerciseCount,
maxPointsForAllExercises: achievableScore,
cosCourseId: contentId,
}),
);
dispatch(readLessonProgresses(allLessonIds));
dispatch(readCourseProgress(contentId));
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const onClickResetCourse = () => {
setResetModalOpen(true);
};
const onClickResetLesson = (e, lessonId) => {
e.preventDefault();
setLessonIdToDelete(lessonId);
setResetModalOpen(true);
};
const onConfirmResetProgress = () => {
if (lessonIdToDelete) {
dispatch(deleteLessonProgress(lessonIdToDelete));
setLessonIdToDelete(null);
} else {
dispatch(deleteCourseProgress(contentId));
}
setResetModalOpen(false);
};
const onCancelResetModal = () => {
setLessonIdToDelete(null);
setResetModalOpen(false);
};
return (
<div className="section" data-course-id={contentId} id="courses">
{isMonoLingualCourse && <LTRContentCourseOverviewStyle />}
<div className="row">
<div className="col-xs-12">
<div className="respHeaderImgContainer course-headline">
<CoursePageHeroImage
imageId={mainContentImageLink.targetId}
imageBasePath={imageBasePath}
>
<h1 className="title">
<I18nText
tabIndex={0}
translation={learningLevelMapper[dkLearningLevel]}
className="identifier"
/>
<I18nText tabIndex={0} translation={name} className="description" />
</h1>
</CoursePageHeroImage>
</div>
</div>
</div>
<div className="row">
<CourseProgress
courseId={+contentId}
finalLessonId={finalLessonId}
onClickResetCourse={onClickResetCourse}
/>
</div>
<div className="row course-list learn-status">
<CourseList {...{ groupedLessons, onClickResetLesson }} />
</div>
<ModalIntegration isOpen={isResetModalOpen}>
<ResetModal
fadeIn={true}
onConfirmClicked={onConfirmResetProgress}
onCancelClicked={onCancelResetModal}
/>
</ModalIntegration>
</div>
);
};

View File

@ -0,0 +1,27 @@
import styled from 'styled-components';
import { useSelector } from 'react-redux';
import { StyledProgressResetButton as ProgressResetButton } from './ProgressResetButton';
import { LearnProgressIcon } from '../../LearnProgressIcon/LearnProgressIcon';
import { findCourseLearnProgressSelector } from '../../../state/progress/learnProgressSelectors';
export const CourseProgress = ({ className, courseId, finalLessonId, onClickResetCourse }) => {
const { courseProgress } = useSelector(findCourseLearnProgressSelector(courseId));
return (
<div className={className}>
<ProgressResetButton onClickReset={onClickResetCourse} />
<LearnProgressIcon learnProgress={courseProgress} finalLessonId={finalLessonId} />
</div>
);
};
export const StyledCourseProgress = styled(CourseProgress)`
display: flex;
justify-content: flex-end;
align-items: center;
padding-right: 25px;
> * {
margin: 14px;
}
`;

View File

@ -0,0 +1,25 @@
import styled from 'styled-components';
import { I18nText } from '../../I18n/I18nText';
import { colors } from '../../../utils/css';
export const ProgressResetButton = ({ className, onClickReset }) => {
return (
<button onClick={e => onClickReset(e)} className={className}>
<I18nText translation="course.reset" />
</button>
);
};
export const StyledProgressResetButton = styled(ProgressResetButton)`
background-color: ${colors.LG_GRAY_2};
border: 1px solid transparent;
color: ${colors.LG_WHITE};
font-size: 12px;
min-height: 31px;
padding: 3px 10px;
`;
export const StyledSmallProgressResetButton = styled(StyledProgressResetButton)`
min-height: 27px;
padding: 1px 7px;
`;

View File

@ -0,0 +1,29 @@
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { StyledMainHeroImage } from '../MainHeroImage/MainHeroImage';
import { StyledTiles } from './Tiles/Tiles';
import { I18nText } from '../I18n/I18nText';
import { readCourseProgresses } from '../../state/progress/learnProgressDuck';
export const Dashboard = ({ data }) => {
const dispatch = useDispatch();
useEffect(() => {
const courseIds = data.map(course => course.id);
dispatch(readCourseProgresses(courseIds));
}, [data, dispatch]);
return (
<main aria-label="dashboard">
<StyledMainHeroImage>
<I18nText isA="h1" tabIndex={0} translation="dashboard.title" className="title" />
<I18nText isA="p" tabIndex={0} translation="dashboard.subTitle" className="description" />
</StyledMainHeroImage>
<StyledTiles
{...{
data,
}}
/>
</main>
);
};

View File

@ -0,0 +1,28 @@
import styled from 'styled-components';
import { colors, resolutions } from '../../../../utils/css';
import { useRtlContext } from '../../../../hooks/useRtl';
const BlankTile = ({ className }) => {
const { isRtl } = useRtlContext();
return <div className={`${className} ${isRtl ? 'rtl' : ''}`} />;
};
export const StyledBlankTile = styled(BlankTile)`
border-top: 1px solid ${colors.LG_WHITE};
border-right: 1px solid ${colors.LG_WHITE};
background: ${colors.DW_LIGHT_BLUE};
text-decoration: none;
@media (max-width: ${resolutions.max.tablet}px) {
height: 180px;
width: 100%;
&.rtl {
border-right-width: 0;
}
}
@media (max-width: ${resolutions.max.mobile}px) {
display: none;
}
`;

View File

@ -0,0 +1,23 @@
import styled from 'styled-components';
import { StyledTile as Tile } from './Tile';
export const MoreCoursesTile = ({ className, backgroundColor }) => (
<Tile
{...{
className,
backgroundColor,
}}
data={{
dkLearningLevel: 0,
name: 'dashboard.dw.title',
subTitle: 'dashboard.dw.text',
href: 'https://www.dw.com/en/learn-german/s-2469', // TODO: to be added to config file
}}
/>
);
export const StyledMoreCoursesTile = styled(MoreCoursesTile)`
.tile-progress {
display: none;
}
`;

View File

@ -0,0 +1,186 @@
import styled from 'styled-components';
import gql from 'graphql-tag';
import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { colors, resolutions } from '../../../../utils/css';
import { I18nText } from '../../../I18n/I18nText';
import { learningLevelMapper } from '../../../../utils/learningLevelUtils';
import { getContentUrl } from '../../../../utils/url/urlFactory';
import { LearnProgressIcon } from '../../../LearnProgressIcon/LearnProgressIcon';
import { findCourseLearnProgressSelector } from '../../../../state/progress/learnProgressSelectors';
import { userSelector } from '../../../../state/user/userSelectors';
import { progressIconColors } from '../../../ProgressIcon/progressIconColors';
export const tileFragment = gql`
fragment Tile on Course {
id
name
language
subTitle
dkLearningLevel
}
`;
export const Tile = ({ className, data }) => {
const { dkLearningLevel, name, subTitle, id } = data;
const { courseProgress } = useSelector(findCourseLearnProgressSelector(id));
const { isLoggedIn } = useSelector(userSelector);
return (
<LinkWrapper {...{ linkData: data, className }}>
{isLoggedIn && (
<div className="tile-progress">
<LearnProgressIcon learnProgress={courseProgress} color={progressIconColors.WHITE} />
</div>
)}
<h2 className="tile-learningLevel">{learningLevelMapper[dkLearningLevel]}</h2>
<I18nText isA="p" translation={name} className="tile-name" aria-label="tile.placement.name" />
<I18nText
isA="p"
translation={subTitle}
className="tile-subTitle"
aria-label="tile.placement.subTitle"
/>
</LinkWrapper>
);
};
const LinkWrapper = ({ children, linkData, className }) => {
return linkData.href ? (
<a className={`${className} tile-container`} href={linkData.href}>
{children}
</a>
) : (
<Link key={linkData.id} className={`${className} tile-container`} to={getContentUrl(linkData)}>
{children}
</Link>
);
};
export const StyledTile = styled(Tile)`
--absElemOffset: 84px;
padding: var(--absElemOffset) 20px 20px 20px;
display: flex;
flex-direction: column;
width: 300px;
height: 250px;
box-sizing: border-box;
position: relative;
border-color: ${colors.LG_WHITE};
border-style: solid;
border-width: 1px 1px 0 0;
background-color: ${({ backgroundColor }) => backgroundColor};
color: ${colors.LG_WHITE};
&:nth-child(4n) {
border-right-width: 0;
}
&.tile-container {
text-decoration: none;
:hover,
:active,
:focus {
color: ${colors.LG_TRANSPARENT_WHITE_50};
}
}
.tile-learningLevel {
font: 400 70px / var(--absElemOffset) Helvetica, Arial, sans-serif;
height: var(--absElemOffset);
text-align: start;
margin: 0;
padding: 0;
position: absolute;
top: 0;
}
.tile-name {
font: 400 23px/25px DWTheAntiquaBBold, Georgia, serif;
margin: 0 0 7px;
width: 100%;
}
.tile-subTitle {
font: 400 16px/25px DWTheAntiquaB, Georgia, serif;
max-height: 60%;
opacity: 0.9;
max-width: 95%;
}
.tile-progress {
position: absolute;
top: 0;
padding-top: 20px;
width: calc(100% - 40px);
height: var(--absElemOffset);
display: flex;
justify-content: flex-end;
}
@media (max-width: ${resolutions.max.tablet}px) {
--absElemOffset: 64px;
height: 180px;
width: 100%;
border-width: 1px 1px 0 0;
&:nth-child(2n) {
border-right-width: 0;
}
.tile-learningLevel {
font: 400 54px/64px Helvetica, Arial, sans-serif;
height: var(--absElemOffset);
}
.tile-name {
font-size: 22px;
}
.tile-subTitle {
font-size: 15px;
}
.tile-progress {
height: var(--absElemOffset);
padding-top: 15px;
}
}
@media (max-width: ${resolutions.max.mobile}px) {
height: 90px;
padding: 10px;
border-width: 1px 0 0 0;
justify-content: center;
.tile-learningLevel {
line-height: 90px;
font-size: 36px;
width: 58px;
height: 90px;
margin: 0 0 0 6px;
}
.tile-name {
position: relative;
font-size: 17px;
line-height: 19px;
font-weight: 400;
margin: 0 25px 3px 64px;
width: calc(100% - 89px);
}
.tile-subTitle {
font-size: 14px;
font-weight: 400;
line-height: 19px;
flex-direction: row;
margin: 0 25px 0 64px;
width: calc(100% - 89px);
}
.tile-progress {
width: calc(100% - 20px);
}
}
`;

View File

@ -0,0 +1,56 @@
import { Fragment } from 'react';
import styled from 'styled-components';
import { colors, resolutions } from '../../../utils/css';
import { StyledTile as Tile } from './Tile/Tile';
import { StyledBlankTile as BlankTile } from './Tile/BlankTile';
import { StyledMoreCoursesTile as MoreCoursesTile } from './Tile/MoreCoursesTile';
import { tileColors } from '../../../constants/tileColors';
import { uniqueId } from '../../../utils/htmlUtils';
export const Tiles = ({ className, data }) => {
return (
<nav className={className}>
{data.map((tileData, index) => {
const tileElement = (
<Tile
key={tileData.id}
backgroundColor={tileColors[index % tileColors.length]}
data={tileData}
/>
);
// check if the number of the fetched tiles is even and if it is,
// then add a blank tile as penultimate
return data.length % 2 === 0 && data.indexOf(tileData) === data.length - 1 ? (
<Fragment key={uniqueId()}>
{tileElement}
<BlankTile />
</Fragment>
) : (
tileElement
);
})}
<MoreCoursesTile backgroundColor={colors.LG_BLUE_5} />
</nav>
);
};
export const StyledTiles = styled(Tiles)`
display: grid;
grid-template-columns: repeat(4, 1fr);
width: 100%;
min-height: 500px;
border-bottom: 1px solid ${colors.LG_WHITE};
@media (min-width: ${resolutions.min.desktop}px) {
width: 1200px;
}
@media (max-width: ${resolutions.max.tablet}px) {
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: ${resolutions.max.mobile}px) {
grid-template-columns: 1fr;
width: 100%;
}
`;

View File

@ -0,0 +1,36 @@
import { useFrontendConfig } from '../../hooks/useFrontendConfig';
import { imgWithFormatFn, format } from '../../utils/imgUtils';
import { resolutions } from '../../utils/css';
import { Picture } from '../Picture/Picture';
export const DwPicture = ({ imageId, ...inProps }) => {
const { imageBasePath } = useFrontendConfig();
const imgWithFormat = imgWithFormatFn(imageId, imageBasePath);
const defaultProps = {
fallbackUrl: imgWithFormat(format.full),
sources: [
{
media: `(min-width: ${resolutions.min.full}px)`,
srcSet: imgWithFormat(format.full),
},
{
media: `(min-width: ${resolutions.min.tabletLandscape}px)`,
srcSet: imgWithFormat(format.lg),
},
{
media: `(min-width: ${resolutions.min.tablet}px)`,
srcSet: imgWithFormat(format.md),
},
{
media: `(min-width: ${resolutions.min.mobile}px)`,
srcSet: imgWithFormat(format.sm),
},
],
};
const props = { ...defaultProps, ...inProps };
return <Picture {...props} />;
};

View File

@ -0,0 +1,168 @@
import styled from 'styled-components';
import gql from 'graphql-tag';
import { useParams } from 'react-router-dom';
import { colors, resolutions } from '../../utils/css';
import { I18nText } from '../I18n/I18nText';
import {
getDwContactUrl,
getDwUrlWithLang,
getFooterLinkForNamedUrl,
getLegalUrl,
} from '../../utils/url/urlFactory';
import { useRtlContext } from '../../hooks/useRtl';
import { legalFragment } from '../LegalNotice/LegalNotice';
export const footerFragment = {
name: 'Footer',
fragment() {
return gql`fragment ${this.name} on Query {
footer(lang: $lang) {
accessibility(appName: "mdl") {
namedUrl
}
policy(appName: "mdl") {
namedUrl
}
${legalFragment.partial()}
}
}
`;
},
};
export const Footer = ({ className, data }) => {
const { isRtl } = useRtlContext();
const { langCode } = useParams();
const rtlClass = isRtl ? 'rtl' : '';
return (
<footer className={`${className} ${rtlClass}`} aria-label="Main Footer">
<ul className={rtlClass}>
{footerData({ langCode, data }).map(footerObject => (
<li key={footerObject.id} className={rtlClass}>
<I18nText
isA="a"
className="footer-link"
translation={footerObject.translation}
href={footerObject.href}
target={footerObject.target}
role={footerObject.role}
aria-label={footerObject.aria}
/>
</li>
))}
</ul>
</footer>
);
};
const footerData = ({ langCode, data }) => [
{
translation: {
key: 'footer.copyright',
parameters: { first: new Date().getFullYear() },
},
id: 0,
aria: 'footer.copyright',
role: 'Firm name',
},
{
translation: 'footer.dw',
id: 1,
href: getDwUrlWithLang({ langCode }),
target: '_blank',
aria: 'footer.dw',
role: 'link',
},
{
translation: 'footer.privacyPolicy',
id: 2,
href: getFooterLinkForNamedUrl({ namedUrl: data.footer.policy.namedUrl }),
target: '_blank',
aria: 'footer.privacyPolicy',
role: 'link',
},
{
translation: 'footer.accessibility',
id: 3,
href: getFooterLinkForNamedUrl({ namedUrl: data.footer.accessibility.namedUrl }),
target: '_blank',
aria: 'footer.accessibility',
role: 'link',
},
{
translation: 'footer.legal',
id: 4,
href: getLegalUrl(langCode),
target: '',
aria: 'footer.legal',
role: 'link',
},
{
translation: 'footer.contact',
id: 5,
href: getDwContactUrl(),
target: '_blank',
aria: 'footer.contact',
role: 'link',
},
];
export const StyledFooter = styled(Footer)`
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-items: center;
width: 100%;
min-height: 90px;
font-family: Helvetica, Arial, sans-serif;
line-height: 45px;
margin: 20px 0;
background: ${colors.LG_BLUE_7};
ul {
display: flex;
flex-flow: row wrap;
flex-direction: row;
max-width: 100%;
list-style-type: none;
padding: 10px 0;
text-align: center;
margin: 0;
}
li {
margin-right: 12px;
padding: 0 0 0 12px;
&.rtl {
direction: rtl;
}
}
.footer-link {
text-decoration: none;
font-size: 15px;
color: ${colors.LG_BLUE_4};
background-color: transparent;
:hover,
:active,
:focus {
color: ${colors.LG_WHITE};
}
}
@media (max-width: ${resolutions.max.tablet}px) {
margin: 0;
}
@media (max-width: ${resolutions.max.mobile}px) {
max-width: 100%;
min-height: 65px;
ul {
justify-content: center;
}
}
`;

View File

@ -0,0 +1,5 @@
import { isServer } from '../../utils/ssr';
export const BrowserOnly = ({ children, ...restProps }) => (
<div {...restProps}>{!isServer() && children}</div>
);

View File

@ -0,0 +1,44 @@
import { useSelector } from 'react-redux';
import config from '../../config';
import { userSelector } from '../../state/user/userSelectors';
import { useLocalStorage } from '../../hooks/useLocalStorage';
import { I18nText } from '../I18n/I18nText';
import { BrowserOnly } from './BrowserOnly';
export const GdprLayer = ({ footerData }) => {
const { isLoggedIn } = useSelector(userSelector);
const [hasAcceptedGdpr, setHasAcceptedGdpr] = useLocalStorage('hasAcceptedGdpr');
const { namedUrl } = footerData.footer.policy || {};
if (isLoggedIn || hasAcceptedGdpr) {
return null;
}
return (
<BrowserOnly>
<div className="cookie" style={{ left: 0 }}>
<div className="cookie__wrap">
<div className="cookie__item">
<I18nText isA="p" className="cookie__text" translation="common.gdpr.overlay.text" />
</div>
<div className="cookie__buttons">
<I18nText
isA="a"
translation="common.gdpr.overlay.moreButton"
className="cookie__btn cookie__btn--more"
href={`${config.dw.baseUrl}${namedUrl}`}
target="_blank"
/>
<I18nText
isA="a"
translation="common.gdpr.overlay.okButton"
className="cookie__btn cookie__btn--ok"
href="#"
onClick={() => setHasAcceptedGdpr(true)}
/>
</div>
</div>
</div>
</BrowserOnly>
);
};

View File

@ -0,0 +1,40 @@
import { Helmet } from 'react-helmet-async';
import { GtmNoScriptFallback } from './GtmNoScriptFallback';
import { useStaticInfoForGtm } from './hooks/useStaticInfoForGtm';
import { gtmDataLayerPartial, TrackingUtils } from './TrackingUtils';
import { useCourseData } from '../../hooks/useCourseData';
export const gtmDataLayerFragment = gtmDataLayerPartial;
export const GtmDataLayer = ({ content, noContentPageId, push = false }) => {
const { gtmLevel1Id: level1Id, macaParam, pageUrl } = useStaticInfoForGtm();
const { courseId } = useCourseData();
const dataLayer = TrackingUtils.generateDataLayer({
content,
macaParam,
level1Id,
pageUrl,
courseId,
noContentPageId,
});
if (push) {
TrackingUtils.pushToGoogleTagManager({
withReset: true,
datalayerObj: {
event: 'onPageChange',
...dataLayer,
},
});
}
return (
<>
<Helmet>
<script>{TrackingUtils.initDataLayerScript(dataLayer)}</script>
</Helmet>
<GtmNoScriptFallback dataLayer={dataLayer} />
</>
);
};

View File

@ -0,0 +1,13 @@
import { Helmet } from 'react-helmet-async';
import { useFrontendConfig } from '../../hooks/useFrontendConfig';
export const GtmLoadScript = () => (
<Helmet>
<script>
{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-${useFrontendConfig().gtmId}');`}
</script>
</Helmet>
);

View File

@ -0,0 +1,23 @@
import { objectToQueryParams } from '../../utils/url/url';
import { useFrontendConfig } from '../../hooks/useFrontendConfig';
export const GtmNoScriptFallback = ({ dataLayer }) => {
const hideIframeStyles = {
display: 'none',
visibility: 'hidden',
};
const { gtmId } = useFrontendConfig();
return (
<noscript>
<iframe
title={`GTM-${gtmId}`}
src={`https://www.googletagmanager.com/ns.html?id=GTM-${gtmId}&${objectToQueryParams(
dataLayer,
)}`}
height="0"
width="0"
style={hideIframeStyles}
/>
</noscript>
);
};

View File

@ -0,0 +1,21 @@
import { isServer } from '../../utils/ssr';
import globals from '../../utils/globals';
import { GtmLoadScript } from './GtmLoadScript';
import { GtmDataLayer } from './GtmDataLayer';
export { gtmDataLayerFragment } from './GtmDataLayer';
export const GtmScriptWithDataLayer = ({ content = {}, noContentPageId }) => {
const isGtmAlreadyInitialized = !isServer() && !!globals.window.dataLayer;
return (
<>
<GtmDataLayer
content={content}
noContentPageId={noContentPageId}
push={isGtmAlreadyInitialized}
/>
{!isGtmAlreadyInitialized && <GtmLoadScript />}
</>
);
};

View File

@ -0,0 +1,139 @@
import gql from 'graphql-tag';
import globals from '../../utils/globals';
const isValidList = list => list?.length > 0;
const addCurrentNavToRootList = ({ nav }) => [...nav.navsToRoot, nav];
// eslint-disable-next-line no-underscore-dangle
const contentTitle = content => (content.__typename === 'Person' ? content.fullName : content.name);
const pickCategoryName = ({ navs, index }) => (navs[index] || {}).name;
export const gtmDataLayerPartial = {
name: 'GtmDataLayer',
partial() {
return `
...on ModelAspect {
id
gtmLanguageCode
gtmContentType
}
...on DeliveryAspect {
gtmContentDate
}
...on NamedAspect {
name
}
...on AssociationsAspect {
navigations {
id
name
navsToRoot {
name
}
}
subjects {
name
}
topics:categories {
originId
}
departments {
name
}
}
`;
},
};
export const mediaInfoForTrackingFragment = {
name: 'MediaInfoForTrackingFragment',
fragment({ type }) {
return gql`fragment ${this.name} on ${type} {
videos {
${gtmDataLayerPartial.partial()}
}
audios {
${gtmDataLayerPartial.partial()}
}
}
`;
},
};
export const generateContentGtmInfo = ({
content,
level1Id,
macaParam,
pageUrl,
courseId,
noContentPageId,
}) => {
const navs = isValidList(content.navigations) ? content.navigations : [{ navsToRoot: [] }];
const alignedNavs = addCurrentNavToRootList({ nav: navs[0] });
return {
categoryLevel1: pickCategoryName({ navs: alignedNavs, index: 0 }),
categoryLevel2: pickCategoryName({ navs: alignedNavs, index: 1 }),
categoryLevel3: pickCategoryName({ navs: alignedNavs, index: 2 }),
contentTitle: contentTitle(content),
contentLanguage: content.gtmLanguageCode,
pageOID: content.id,
contentType: content.gtmContentType,
date: content.gtmContentDate,
subject: content.subjects ? content.subjects[0]?.name : {},
topicIds: content.topics?.map(topic => topic.originId),
pageSID: navs[0].id,
departmentName: content.departments ? content.departments[0]?.name : {},
categoryType: 1,
displayForm: 5,
level1ID: level1Id,
macaParam,
pageUrl,
courseId,
noContentPageId,
};
};
export const TrackingUtils = {
initDataLayerScript: datalayer => {
const initialDataLayer = {
...datalayer,
event: 'onPageChange',
};
return `
if(!window.dataLayer) {
window.dataLayer = [${JSON.stringify(initialDataLayer)}]
};`.trim();
},
generateDataLayer: ({ content, level1Id, macaParam, pageUrl, courseId, noContentPageId }) => ({
pageData: {
...generateContentGtmInfo({
content,
level1Id,
macaParam,
pageUrl,
courseId,
noContentPageId,
}),
embeddings: {},
},
}),
pushToGoogleTagManager: ({ datalayerObj, withReset = false }) => {
setTimeout(() => {
if (withReset) {
TrackingUtils.resetDataLayer();
}
// eslint-disable-next-line fp/no-mutating-methods
globals.window.dataLayer?.push(datalayerObj);
}, 0);
},
resetDataLayer: () => {
// eslint-disable-next-line fp/no-mutating-methods
globals.window.dataLayer.push(function dontUseArrowFunction() {
this.reset();
});
},
};

View File

@ -0,0 +1,18 @@
import { useEffect } from 'react';
import { TrackingUtils } from '../TrackingUtils';
const eventName = 'addEmbeddings';
export const useAddEmbeddingsToGtm = ({ dictionary }) => {
useEffect(() => {
TrackingUtils.pushToGoogleTagManager({
datalayerObj: {
event: eventName,
pageData: {
embeddings: dictionary,
},
},
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};

View File

@ -0,0 +1,17 @@
import { useStaticInfoForGtm } from './useStaticInfoForGtm';
import { generateContentGtmInfo } from '../TrackingUtils';
import { useAddEmbeddingsToGtm } from './useAddEmbeddingsToGtm';
export const useMediaTracking = content => {
const { level1Id, macaParam, pageUrl } = useStaticInfoForGtm();
const contentDictionary = {
[content.id]: generateContentGtmInfo({
content,
level1Id,
macaParam,
pageUrl,
}),
};
useAddEmbeddingsToGtm({ dictionary: contentDictionary });
};

View File

@ -0,0 +1,17 @@
import { useStaticInfoForGtm } from './useStaticInfoForGtm';
import { generateContentGtmInfo } from '../TrackingUtils';
import { useAddEmbeddingsToGtm } from './useAddEmbeddingsToGtm';
export const useMediaTracking = content => {
const { level1Id, macaParam, pageUrl } = useStaticInfoForGtm();
const contentDictionary = {
[content.id]: generateContentGtmInfo({
content,
level1Id,
macaParam,
pageUrl,
}),
};
useAddEmbeddingsToGtm({ dictionary: contentDictionary });
};

View File

@ -0,0 +1,11 @@
import { useGlobalsContext } from '../../../context/GlobalsContext';
import { useLocationQueryParam } from '../../../hooks/useLocationQueryParam';
import { useFrontendConfig } from '../../../hooks/useFrontendConfig';
export const useStaticInfoForGtm = () => {
const pageUrl = useGlobalsContext().window.location.href.split('?')[0];
const macaParam = useLocationQueryParam('maca');
const { gtmLevel1Id } = useFrontendConfig();
return { gtmLevel1Id, macaParam, pageUrl };
};

View File

@ -0,0 +1,6 @@
import { memo } from 'react';
import { GtmScriptWithDataLayer as NotMemoGtmScriptWithDataLayer } from './GtmScriptWithDataLayer';
export { gtmDataLayerFragment } from './GtmScriptWithDataLayer';
export const GtmScriptWithDataLayer = memo(NotMemoGtmScriptWithDataLayer);

View File

@ -0,0 +1,30 @@
import { wrapTablesWithDiv } from '../../utils/replacers/htmlReplacer';
import { useKnowledgePlaceholderReplacementEffect } from '../Modal/KnowledgeModal/useKnowledgePlaceholderReplacementEffect';
import { WithRenderDelay } from '../WithRenderDelay/WithRenderDelay';
import { replacePlaceholderImagesWithResponsivePictures } from '../../utils/replacers/imageReplacer';
import { RichText } from '../RichText/RichText';
import { ContentHeadline } from '../ContentContainer/ContentHeadline/ContentHeadline';
export const GrammarItem = ({ data }) => {
const { name, text, knowledges } = data.content;
const markup = wrapTablesWithDiv(text);
useKnowledgePlaceholderReplacementEffect({ knowledges });
return (
<div className="section exercise-container vocabulary copy">
<ContentHeadline>
<h2 dir="auto">{name}</h2>
</ContentHeadline>
<div className="row vocabulary">
<div className="col-sm-offset-1 col-sm-10 col-lg-offset-2 col-lg-8">
<WithRenderDelay
actionToBeDelayed={replacePlaceholderImagesWithResponsivePictures}
delayTime={10}
render={() => <RichText content={markup} />}
/>
</div>
</div>
</div>
);
};

Some files were not shown because too many files have changed in this diff Show More