├── .babelrc
├── .editorconfig
├── .env
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .gitlab-ci.yml
├── README.md
├── README_EN.md
├── TODO.md
├── __mocks__
├── fileMock.js
└── styleMock.js
├── create.html
├── font-awesome.config.js
├── index.html
├── list.html
├── news.html
├── package-lock.json
├── package.json
├── src
├── css
│ ├── base
│ │ ├── _base.scss
│ │ ├── _mixins.scss
│ │ ├── _normalize.scss
│ │ ├── _variables.scss
│ │ ├── colors.scss
│ │ ├── main.scss
│ │ └── modules
│ │ │ ├── _alert.scss
│ │ │ ├── _arguments.scss
│ │ │ ├── _barometer.scss
│ │ │ ├── _discussion-list.scss
│ │ │ ├── _discussion-participant.scss
│ │ │ ├── _discussion.scss
│ │ │ ├── _dropdown.scss
│ │ │ ├── _forms.scss
│ │ │ ├── _messages.scss
│ │ │ ├── _modal.scss
│ │ │ ├── _pagination.scss
│ │ │ ├── _star-ratings.scss
│ │ │ ├── _statement-list.scss
│ │ │ ├── _tags.scss
│ │ │ ├── _toggle.scss
│ │ │ └── _view.scss
│ └── themes
│ │ ├── bjv.scss
│ │ ├── brabbl.scss
│ │ ├── brabbl2021.scss
│ │ ├── eyp.scss
│ │ ├── jugendinfo.scss
│ │ ├── main.scss
│ │ └── vorwaerts.scss
├── fonts
│ ├── FontAwesome.otf
│ ├── _animated.scss
│ ├── _bordered-pulled.scss
│ ├── _core.scss
│ ├── _fixed-width.scss
│ ├── _icons.scss
│ ├── _larger.scss
│ ├── _list.scss
│ ├── _mixins.scss
│ ├── _path.scss
│ ├── _rotated-flipped.scss
│ ├── _stacked.scss
│ ├── _variables.scss
│ ├── font-awesome.scss
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
├── img
│ ├── .keep
│ ├── Checkmark.png
│ └── arrow.png
└── js
│ ├── actions
│ ├── app.js
│ └── async.js
│ ├── api.js
│ ├── components
│ ├── __mocks__
│ │ └── app.mock.js
│ ├── __tests__
│ │ └── app.spec.js
│ ├── app.jsx
│ ├── argument
│ │ ├── argument-buttons.jsx
│ │ ├── argument-list.jsx
│ │ ├── argument.jsx
│ │ ├── index.js
│ │ └── starrating.jsx
│ ├── avatar-container.jsx
│ ├── base-detail-view.jsx
│ ├── common
│ │ ├── atoms
│ │ │ ├── CAutoHeightAnimator
│ │ │ │ └── index.js
│ │ │ ├── CButton
│ │ │ │ └── index.js
│ │ │ ├── CCircularProgress
│ │ │ │ └── index.jsx
│ │ │ ├── CCollapsable
│ │ │ │ └── index.jsx
│ │ │ ├── CImageLoader
│ │ │ │ └── index.js
│ │ │ ├── CMarkupParser
│ │ │ │ └── index.jsx
│ │ │ ├── CPagination
│ │ │ │ └── index.js
│ │ │ ├── CRemovableList
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── custom-widgets
│ │ │ └── CKEditor.jsx
│ │ ├── index.js
│ │ └── newforms
│ │ │ ├── BoundField.js
│ │ │ ├── ErrorList.js
│ │ │ ├── ErrorObject.js
│ │ │ ├── Field.js
│ │ │ ├── Form.js
│ │ │ ├── FormSet.js
│ │ │ ├── Widget.js
│ │ │ ├── components
│ │ │ ├── FormRow.jsx
│ │ │ ├── ProgressMixin.jsx
│ │ │ ├── RenderForm.jsx
│ │ │ └── RenderFormSet.jsx
│ │ │ ├── constants.js
│ │ │ ├── env.js
│ │ │ ├── fields
│ │ │ ├── BaseTemporalField.js
│ │ │ ├── BooleanField.js
│ │ │ ├── CharField.js
│ │ │ ├── ChoiceField.js
│ │ │ ├── ComboField.js
│ │ │ ├── DateField.js
│ │ │ ├── DateTimeField.js
│ │ │ ├── DecimalField.js
│ │ │ ├── EmailField.js
│ │ │ ├── FileField.js
│ │ │ ├── FilePathField.js
│ │ │ ├── FloatField.js
│ │ │ ├── GenericIPAddressField.js
│ │ │ ├── IPAddressField.js
│ │ │ ├── ImageField.js
│ │ │ ├── IntegerField.js
│ │ │ ├── MultiValueField.js
│ │ │ ├── MultipleChoiceField.js
│ │ │ ├── MultipleFileField.js
│ │ │ ├── NullBooleanField.js
│ │ │ ├── RegexField.js
│ │ │ ├── SlugField.js
│ │ │ ├── SplitDateTimeField.js
│ │ │ ├── TimeField.js
│ │ │ ├── TypedChoiceField.js
│ │ │ ├── TypedMultipleChoiceField.js
│ │ │ └── URLField.js
│ │ │ ├── formats.js
│ │ │ ├── forms
│ │ │ ├── DeclarativeFieldsMeta.js
│ │ │ └── isFormAsync.js
│ │ │ ├── locales.js
│ │ │ ├── newforms.js
│ │ │ ├── util.js
│ │ │ └── widgets
│ │ │ ├── CheckboxInput.js
│ │ │ ├── CheckboxSelectMultiple.js
│ │ │ ├── ClearableFileInput.js
│ │ │ ├── DateInput.js
│ │ │ ├── DateTimeBaseInput.js
│ │ │ ├── DateTimeInput.js
│ │ │ ├── EmailInput.js
│ │ │ ├── FileInput.js
│ │ │ ├── HiddenInput.js
│ │ │ ├── Input.js
│ │ │ ├── MultiWidget.js
│ │ │ ├── MultipleHiddenInput.js
│ │ │ ├── NullBooleanSelect.js
│ │ │ ├── NumberInput.js
│ │ │ ├── PasswordInput.js
│ │ │ ├── RadioSelect.js
│ │ │ ├── Select.js
│ │ │ ├── SelectMultiple.js
│ │ │ ├── SplitDateTimeWidget.js
│ │ │ ├── SplitHiddenDateTimeWidget.js
│ │ │ ├── SubWidget.js
│ │ │ ├── TextInput.js
│ │ │ ├── Textarea.js
│ │ │ ├── TimeInput.js
│ │ │ ├── URLInput.js
│ │ │ ├── inputs
│ │ │ ├── CheckboxChoiceInput.js
│ │ │ ├── ChoiceInput.js
│ │ │ └── RadioChoiceInput.js
│ │ │ └── renderers
│ │ │ ├── CheckboxFieldRenderer.js
│ │ │ ├── ChoiceFieldRenderer.js
│ │ │ ├── RadioFieldRenderer.js
│ │ │ └── RendererMixin.js
│ ├── discussion
│ │ ├── __tests__
│ │ │ ├── barometer.spec.js
│ │ │ └── discussion-button.spec.js
│ │ ├── barometer.jsx
│ │ ├── countdown-timer.jsx
│ │ ├── discussion-argument.jsx
│ │ ├── discussion-button.jsx
│ │ ├── discussion-item.jsx
│ │ ├── discussion-meta.jsx
│ │ ├── discussion-time-left.jsx
│ │ ├── discussion.jsx
│ │ ├── index.js
│ │ ├── list-discussion-item.jsx
│ │ ├── statement-detail.jsx
│ │ ├── statement-item.jsx
│ │ ├── survey-list.jsx
│ │ ├── survey-statement-item.jsx
│ │ └── utils.js
│ ├── dropdown.js
│ ├── forms
│ │ ├── argument.jsx
│ │ ├── containers
│ │ │ ├── argument.jsx
│ │ │ ├── discussion.jsx
│ │ │ ├── discussion_list.jsx
│ │ │ ├── discussion_participant.jsx
│ │ │ ├── invite_participant.jsx
│ │ │ ├── login.jsx
│ │ │ ├── profile.jsx
│ │ │ ├── resetpassword.jsx
│ │ │ ├── signup.jsx
│ │ │ ├── social_buttons.jsx
│ │ │ ├── statement.jsx
│ │ │ └── utils.js
│ │ ├── discussion.jsx
│ │ ├── discussion_list.jsx
│ │ ├── index.js
│ │ ├── invite_participant.jsx
│ │ ├── login.jsx
│ │ ├── profile.jsx
│ │ ├── resetpassword.jsx
│ │ ├── signup.jsx
│ │ ├── statement.jsx
│ │ └── toggle.jsx
│ ├── header.jsx
│ ├── login.jsx
│ ├── markup_wording.jsx
│ ├── media.jsx
│ ├── modals
│ │ ├── alert.jsx
│ │ ├── argument.jsx
│ │ ├── barometer.jsx
│ │ ├── data_policy.jsx
│ │ ├── delete_discussion.jsx
│ │ ├── delete_statement.jsx
│ │ ├── discussion.jsx
│ │ ├── discussion_list.jsx
│ │ ├── export_discussion.jsx
│ │ ├── export_statistics.jsx
│ │ ├── iframe.jsx
│ │ ├── invite_participant.jsx
│ │ ├── login.jsx
│ │ ├── media.jsx
│ │ ├── modal-selector.jsx
│ │ ├── modal.jsx
│ │ ├── password.jsx
│ │ ├── profile.jsx
│ │ ├── reset_discussion.jsx
│ │ ├── signup.jsx
│ │ └── statement.jsx
│ ├── navigations
│ │ └── navigator.jsx
│ ├── options.jsx
│ ├── services
│ │ └── live-update-manager.jsx
│ ├── tag-select.jsx
│ ├── timelimit-select.jsx
│ ├── user-message.jsx
│ └── views
│ │ ├── detail-view.jsx
│ │ ├── info-view.jsx
│ │ ├── list-view.jsx
│ │ ├── login-view.jsx
│ │ ├── news-view.jsx
│ │ ├── reset-password-view.jsx
│ │ └── signup-view.jsx
│ ├── config
│ ├── development.config.js
│ ├── index.js
│ ├── new_staging.config.js
│ └── production.config.js
│ ├── constants.js
│ ├── helpers
│ ├── discussionExcelExport.js
│ ├── index.js
│ └── statisticsExcelExport.js
│ ├── i18n.js
│ ├── init.dev.js
│ ├── init.js
│ ├── init.prod.js
│ ├── reducers
│ ├── alert.js
│ ├── argument.js
│ ├── customer.js
│ ├── discussion.js
│ ├── index.js
│ ├── modal.js
│ ├── notification.js
│ ├── translators.js
│ └── user.js
│ ├── store
│ ├── configureStore.dev.js
│ ├── configureStore.js
│ └── configureStore.prod.js
│ └── utils.js
├── test
└── setup.js
├── themes.md
├── webpack.config.js
├── webpack.new_staging.config.js
└── webpack.production.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-react", "@babel/preset-env"],
3 | "plugins": ["transform-class-properties"]
4 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | trim_trailing_whitespace = true
8 |
9 | [*.{py,rst,ini}]
10 | indent_style = space
11 | indent_size = 4
12 |
13 | [*.{html,js,coffee,yml,jsx,scss}]
14 | indent_style = space
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | APP_ENV=new_staging
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # node_modules are ignored by default
2 |
3 | # Ignore build folder
4 | build
5 | dist
6 | .git
7 |
8 | # Ignore config stuff
9 | server.*.js
10 | *config.js
11 | serviceworker.js
12 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "globals": {
8 | "Atomics": "readonly",
9 | "SharedArrayBuffer": "readonly"
10 | },
11 | "parser": "babel-eslint",
12 | "parserOptions": {
13 | "ecmaFeatures": {
14 | "jsx": true
15 | },
16 | "ecmaVersion": 2018,
17 | "sourceType": "module"
18 | },
19 | "rules": {
20 | "max-len": [1, 120, 2, {ignoreComments: true}],
21 | "react/display-name": 0,
22 | "prefer-const": 0,
23 | "prefer-template": 0,
24 | "no-console": 0,
25 | "new-cap": 0,
26 | "no-param-reassign": 0,
27 | "no-else-return": 0,
28 | "object-shorthand": 0,
29 | "prefer-arrow-callback": 0,
30 | "camelcase": 0,
31 | "quote-props": 0,
32 | "one-var": 0,
33 | "consistent-return": 0,
34 | "react/no-underscore-dangle": 0,
35 | "react/jsx-pascal-case": 0,
36 | "react/prefer-stateless-function": 0,
37 | "react/prefer-es6-class": 0,
38 | "react/no-multi-comp": 0,
39 | "react/prop-types": 0,
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ------------------------- Linux
2 | *~
3 |
4 | # KDE
5 | .directory
6 |
7 | # ------------------------- OSX
8 | .DS_Store
9 |
10 | # Thumbnails
11 | ._*
12 |
13 | # Files that might appear on external disk
14 | .Spotlight-V100
15 | .Trashes
16 |
17 | # ------------------------- vim
18 | .*.sw[a-z]
19 | *.un~
20 | Session.vim
21 |
22 |
23 | # ------------------------- Python
24 | *.py[co]
25 |
26 | # Packages
27 | *.egg
28 | *.egg-info
29 | dist
30 | build
31 | eggs
32 | parts
33 | bin
34 | var
35 | sdist
36 | coverage
37 | develop-eggs
38 | .installed.cfg
39 |
40 | # Installer logs
41 | pip-log.txt
42 |
43 | # Unit test / coverage reports
44 | .coverage
45 | .tox
46 |
47 | #Translations
48 | *.mo
49 |
50 |
51 | # ------------------------ Django
52 | *.log
53 | *.pot
54 | *.pyc
55 | settings_local.py
56 |
57 | # ------------------------ Custom
58 | .cache
59 | .tmp
60 | *.sqlite3
61 | htmlcov
62 | node_modules
63 | frontend/lib
64 | docs/build
65 | *.orig
66 | *.sql
67 | *.tox/*
68 | media
69 | *.log
70 | npm-debug*
71 | .idea
72 | package-backup.json
73 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | before_script:
2 | - export LC_ALL=en_US.UTF-8
3 | - export PATH=$ENV_PATH/bin:$PATH
4 |
5 | stages:
6 | - test
7 |
8 | test_job:
9 | stage: test
10 | script:
11 | - bash -c '. ~/.nvm/nvm.sh ; nvm use 5.0 ; npm install -s > /dev/null'
12 | - bash -c '. ~/.nvm/nvm.sh ; nvm use 5.0 ; npm test'
13 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/README_EN.md
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | Webpack / Deployment
2 | --------------------
3 | * Better seperation of configs and commands for the different environments
4 |
5 | AXIOS / API-Interaction
6 | -----------------------
7 | * Improve API client once this is available in a stable release: https://github.com/mzabriskie/axios#axioscreateconfig
8 | * Better organization of API module or use API-Client in directly in actions and remove API module
9 | * Global intercept for server errors (5xx) => Decide how to handle them best (user message, error page, ...)
10 |
11 | REDUX/State-Management
12 | ----------------------
13 |
14 | * Use redux for async stuff (More granular actions)
15 |
16 | Testing
17 | -------
18 |
19 | * Setup test tools
20 | * Add tests for reducers
21 | * Add tests for everything else
22 |
23 | Deployment/Setup
24 | ----------------
25 |
26 | * Check babel stage (really 0?: https://babeljs.algolia.com/docs/usage/experimental/)
27 | * Check raven/sentry integration
28 | * Improve build size:
29 | - http://moduscreate.com/optimizing-react-es6-webpack-production-build/
30 | - https://github.com/robertknight/webpack-bundle-size-analyzer
31 | * Check better separation of client builds (deploy new features/versions only for some clients)
32 | - Tag versions and map clients to tags?
33 |
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/__mocks__/fileMock.js
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
--------------------------------------------------------------------------------
/create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Brabbl
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/font-awesome.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Default for the style loading
3 | styleLoader: 'style-loader!css-loader!sass-loader',
4 |
5 | styles: {
6 | mixins: true,
7 | 'bordered-pulled': true,
8 | core: true,
9 | 'fixed-width': true,
10 | icons: true,
11 | larger: true,
12 | list: true,
13 | path: true,
14 | 'rotated-flipped': true,
15 | animated: true,
16 | stacked: true
17 | }
18 | };
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Brabbl
10 |
11 |
12 | Below this simple paragraph, there should be the faboulous 'brabbl' widget, showing a discussion with externalID 'demo-discussion-1' (customer 'brabbl-dev-customer-1').
13 |
14 |
15 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Brabbl
10 |
11 |
12 | Below this simple paragraph, there should be the faboulous 'brabbl' widget, showing a list of all existing discussions for customer 'brabbl-dev-customer-1'.
13 |
14 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/news.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Brabbl
10 |
11 |
12 | Below this simple paragraph, there should be the faboulous 'brabbl' widget, showing a list of all existing discussions for customer 'brabbl-dev-customer-1'.
13 |
14 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/css/base/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Media query generator - copied from https://github.com/paranoida/sass-mediaqueries
2 | @mixin mq($args...) {
3 | $media-type: 'only screen';
4 | $media-type-key: 'media-type';
5 | $args: keywords($args);
6 | $expr: '';
7 |
8 | @if map-has-key($args, $media-type-key) {
9 | $media-type: map-get($args, $media-type-key);
10 | $args: map-remove($args, $media-type-key);
11 | }
12 |
13 | @each $key, $value in $args {
14 | @if $value {
15 | $expr: "#{$expr} and (#{$key}: #{$value})";
16 | }
17 | }
18 |
19 | @media #{$media-type} #{$expr} {
20 | @content;
21 | }
22 | }
23 |
24 | @mixin bp-large {
25 | @include mq($min-width: $breakpoint-large) {
26 | @content;
27 | }
28 | }
29 |
30 | @mixin bp-ml-large {
31 | @include mq($min-width: $breakpoint-ml) {
32 | @content;
33 | }
34 | }
35 |
36 | @mixin bp-ml {
37 | @include mq($min-width: $breakpoint-ml, $max-width: $breakpoint-large - 1px) {
38 | @content;
39 | }
40 | }
41 |
42 | @mixin bp-medium-large{
43 | @include mq($min-width: $breakpoint-medium) {
44 | @content;
45 | }
46 | }
47 |
48 | @mixin bp-medium {
49 | @include mq($min-width: $breakpoint-medium, $max-width: $breakpoint-large - 1px) {
50 | @content;
51 | }
52 | }
53 |
54 | @mixin bp-small-medium {
55 | @include mq($max-width: $breakpoint-ml - 1px) {
56 | @content;
57 | }
58 | }
59 |
60 | @mixin bp-small{
61 | @include mq($max-width: $breakpoint-small) {
62 | @content;
63 | }
64 | }
65 |
66 | %clearfix {
67 | *zoom: 1;
68 | &:before, &:after {
69 | content: " ";
70 | display: table;
71 | }
72 | &:after {
73 | clear: both;
74 | }
75 | }
76 |
77 | @mixin fadeIn($duration: 200ms) {
78 | opacity: 0;
79 | transition: opacity $duration ease-in-out;
80 |
81 | &.show {
82 | opacity: 1;
83 | }
84 | }
85 |
86 | @mixin vertical-align($position: relative) {
87 | position: $position;
88 | top: 50%;
89 | transform: translateY(-50%);
90 | }
91 |
92 | @mixin tooltip-arrow($color: $black, $size: 5px) {
93 | &:after {
94 | top: 100%;
95 | left: 50%;
96 | border: solid transparent;
97 | content: " ";
98 | height: 0;
99 | width: 0;
100 | position: absolute;
101 | pointer-events: none;
102 | border-color: transparent;
103 | border-top-color: $color;
104 | border-width: $size;
105 | margin-left: -$size;
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/css/base/_variables.scss:
--------------------------------------------------------------------------------
1 | // Breakpoints
2 | $breakpoint-large: 960px;
3 | $breakpoint-ml: 780px;
4 | $breakpoint-medium: 480px;
5 | $breakpoint-small: 320px;
6 |
7 | $modal-border-radius: 10px;
8 | $button-border-radius: 6px;
9 |
--------------------------------------------------------------------------------
/src/css/base/colors.scss:
--------------------------------------------------------------------------------
1 | // Colors
2 | $white: #FFFFFF;
3 | $black: #111111;
4 | $dark-grey: #858585;
5 | $medium-grey: #EAEAEA;
6 | $light-grey: #F5F7F7;
7 | $red: #D31114;
8 | $green: #0F9601;
9 | $yellow: #F5A71B;
10 | $widget-background-color: #f2f2f2;
11 | $modal-background-color: #f2f2f2;
12 | $red-orange: #FF741A;
--------------------------------------------------------------------------------
/src/css/base/main.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 | @import 'colors';
3 | @import 'mixins';
4 | @import 'modules/modal';
5 | @import 'modules/alert';
6 |
7 | #brabbl-widget,
8 | .ReactModal__Overlay {
9 | @import 'normalize';
10 | @import 'base';
11 | @import 'modules/barometer';
12 | @import 'modules/discussion';
13 | @import 'modules/discussion-list';
14 | @import 'modules/statement-list';
15 | @import 'modules/discussion-participant';
16 | @import 'modules/arguments';
17 | @import 'modules/forms';
18 | @import 'modules/star-ratings';
19 | @import 'modules/toggle';
20 | @import 'modules/messages';
21 | @import 'modules/dropdown';
22 | @import 'modules/tags';
23 | @import 'modules/view';
24 | @import 'modules/pagination';
25 | }
26 |
27 | #brabbl-widget {
28 | background-color: $widget-background-color;
29 | border-radius: $modal-border-radius;
30 | font-weight: 400;
31 | padding: 16px;
32 | @include bp-small-medium {
33 | padding: 8px;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/css/base/modules/_alert.scss:
--------------------------------------------------------------------------------
1 |
2 | .alert_modal_content {
3 | left:0 !important;
4 | right:0 !important;
5 | width: 380px;
6 | padding: 5px 5px 15px !important;
7 | margin-left: auto;
8 | margin-right: auto;
9 | top: 35%;
10 | background-color: white;
11 | > div {
12 | margin: 5px 5px 5px;
13 | max-width: 380px;
14 | }
15 | -webkit-transition: 0.2s ease-in-out;
16 | -moz-transition: 0.2s ease-in-out;
17 | -o-transition: 0.2s ease-in-out;
18 | transition: 0.2s ease-in-out;
19 | border-radius: 6px;
20 | }
21 | .alert-modal {
22 | display: flex;
23 | flex-direction: column;
24 | align-items: center;
25 | width: 360px;
26 | button {
27 | flex: 1;
28 | align-items: center;
29 | justify-content: center;
30 | height: 40px;
31 | width: 240px;
32 | margin-bottom: 16px;
33 | }
34 | }
35 |
36 | .alert-modal-title {
37 | font-size: 14px;
38 | margin-top: 16px;
39 | }
40 | .alert-modal-message {
41 | font-size: 14px;
42 | margin-left: 16px;
43 | margin-right: 16px;
44 | margin-top: 16px;
45 | margin-bottom: 16px;
46 | }
--------------------------------------------------------------------------------
/src/css/base/modules/_discussion-participant.scss:
--------------------------------------------------------------------------------
1 | .participant-section {
2 | border-bottom: 1px solid $dark-grey;
3 | margin-bottom: 24px;
4 | }
5 | .manage-participant {
6 | .collapsable-section {
7 | .collapsable-section-title {
8 | h2 {
9 | font-size: 14px;
10 | }
11 | .icon {
12 | font-size: 20px;
13 | margin-left: 8px;
14 | }
15 | }
16 | }
17 | }
18 | .participant-names {
19 | font-style: italic;
20 | font-size: 14px;
21 | }
22 | .participant-list {
23 | margin-top: 32px;
24 | .participant-checkbox {
25 | width: 32px;
26 | color: #9BCF67;
27 | }
28 | .participant-item {
29 | margin-left: 32px;
30 | display: flex;
31 | flex-direction: row;
32 | align-items: center;
33 | .avatar-non-select {
34 | opacity: 0.8;
35 | }
36 | .participant-info {
37 | display: flex;
38 | flex: 1;
39 | flex-direction: column;
40 | align-items: left;
41 | margin-top: 4px;
42 | margin-left: 4px;
43 | .name {
44 | font-size: 12px;
45 | font-weight: normal;
46 | }
47 | .email {
48 | margin-top: -6px;
49 | font-size: 11px;
50 | }
51 | .selected {
52 | color: $black;
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/css/base/modules/_dropdown.scss:
--------------------------------------------------------------------------------
1 | .dropdown-select {
2 | font-size: 1.07em;
3 |
4 | .dropdown-active {
5 | display: block;
6 | cursor: pointer;
7 | height: 20px;
8 |
9 | i {
10 | margin-left: 2px;
11 | vertical-align: top;
12 | }
13 | }
14 |
15 | ul {
16 | position: absolute;
17 | right: 0;
18 | z-index: 50;
19 | padding: 1em;
20 | margin: 0;
21 | background-color: $light-grey;
22 | border: 1px solid $medium-grey;
23 | display: none;
24 | list-style: none;
25 |
26 | &.open {
27 | display: block;
28 | }
29 |
30 | li {
31 | padding: 5px;
32 | color: $dark-grey;
33 | cursor: pointer;
34 | list-style: none;
35 | font-size: 1em;
36 | line-height: 1.2em;
37 |
38 | &:hover {
39 | color: darken($dark-grey, 20%);
40 | }
41 | }
42 | }
43 | }
44 |
45 | .discussion-header-tags {
46 | float: left;
47 | ul {
48 | left: 0 !important;
49 | width: 120px;
50 | }
51 | }
52 |
53 |
54 | .discussion-header-time-limit {
55 | float: right;
56 | ul {
57 | right: 0;
58 | width: 120px;
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/src/css/base/modules/_messages.scss:
--------------------------------------------------------------------------------
1 | .crouton {
2 | font-size: 1.33em;
3 | color: #FFF;
4 | line-height: 140%;
5 | border-radius: 2px;
6 | position: fixed;
7 | top: 0px;
8 | left: 0px;
9 | right: 0px;
10 | width: 100%;
11 | text-align: center;
12 | z-index: 999999999;
13 | margin: 0 auto;
14 |
15 | div {
16 | padding: 10px;
17 | &.user-message {background-color: $base-color; color: $white}
18 | &.info { background-color: #1F8DD6; }
19 | &.success { background-color: #50CD84; }
20 | &.warning { background-color: #FF8859; }
21 | &.error { background-color: #FF5F5F; }
22 | span {
23 | display: block;
24 | margin-bottom: 0;
25 | margin-left: 3px;
26 | }
27 |
28 | .buttons {
29 | height: 0;
30 | padding: 0;
31 |
32 | button {
33 | position: absolute;
34 | right: 2px;
35 | top: 2px;
36 | color: $white;
37 | padding: 0;
38 | margin: 6px 10px 0 0;
39 | border-color: $base-color;
40 |
41 | &:hover {
42 | background-color: $base-color;
43 | color: $white
44 | }
45 |
46 | &.btn,
47 | &.retry,
48 | &.close,
49 | &.cancel,
50 | &.ignore {
51 | padding: 1px;
52 | border: 1px solid transparent;
53 | }
54 | &.retry {
55 | border-color: #FFF;
56 | }
57 | &.btn {
58 | &.close {
59 | border-color: $base-color;
60 | margin-left: 10px;
61 | }
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/css/base/modules/_statement-list.scss:
--------------------------------------------------------------------------------
1 | .statement-item-container {
2 | background-color: $white;
3 | margin-top: 0px;
4 | position: relative;
5 | cursor: pointer;
6 | border-radius: $modal-border-radius;
7 | -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
8 | -moz-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
9 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
10 | min-height: 100px;
11 | &:hover {
12 | h3 a {
13 | color: $list-entry-title-color !important;
14 | }
15 | }
16 |
17 | }
18 |
19 |
20 | .statement-detail {
21 | padding: 16px;
22 | }
23 |
24 | .c-auto-height-animator {
25 | overflow: hidden;
26 | padding: 5px;
27 | transition: height 0.4s ease-out;
28 | }
--------------------------------------------------------------------------------
/src/css/base/modules/_tags.scss:
--------------------------------------------------------------------------------
1 | .react-tagsinput {
2 | background-color: #fff;
3 | border: 1px solid #ccc;
4 | overflow: hidden;
5 | padding-left: 5px;
6 | padding-top: 5px;
7 | border-radius: $button-border-radius;
8 | }
9 |
10 | .react-tagsinput-tag {
11 | background-color: #f6f8f2;
12 | border: 1px solid #b9bcb5;
13 | color: #2c2c2c;
14 | border-radius: 2px;
15 | display: inline-block;
16 | font-family: sans-serif;
17 | font-size: 1em;
18 | font-weight: 400;
19 | margin-bottom: 5px;
20 | margin-right: 5px;
21 | padding: 5px;
22 | border-radius: $button-border-radius;
23 | }
24 |
25 | .react-tagsinput-remove {
26 | cursor: pointer;
27 | font-weight: bold;
28 | }
29 |
30 | .react-tagsinput-tag a::before {
31 | content: " ×";
32 | }
33 |
34 | .react-tagsinput-input {
35 | background: transparent;
36 | border: 0;
37 | color: #777;
38 | font-family: sans-serif;
39 | font-size: 1em;
40 | font-weight: 400;
41 | margin-bottom: 6px;
42 | margin-top: 1px;
43 | outline: none;
44 | padding: 5px;
45 | width: 80px;
46 | }
47 |
--------------------------------------------------------------------------------
/src/css/base/modules/_toggle.scss:
--------------------------------------------------------------------------------
1 | * {
2 | -webkit-tap-highlight-color: rgba(0,0,0,0); /* Stops flash on tap iOS */
3 | }
4 |
5 | .toggle {
6 | max-height: 0;
7 | max-width: 0;
8 | opacity: 0;
9 | }
10 |
11 | .toggle + label {
12 | cursor: pointer;
13 | display: block;
14 | position: relative;
15 | box-shadow: inset 0 0 0px 1px #d5d5d5;
16 | text-indent: -5000px;
17 | height: 30px; width: 50px;
18 | border-radius: 15px;
19 | background-color: $base-color;
20 | }
21 |
22 | .toggle + label:before {
23 | content: '';
24 | position: absolute;
25 | display: block;
26 | height: 30px; width: 30px;
27 | top: 0; left: 0;
28 | border-radius: 15px;
29 | background: $green;
30 | transition: .25s ease-in-out;
31 | }
32 |
33 | .toggle + label:after {
34 | content: '';
35 | position: absolute;
36 | display: block;
37 | height: 30px; width: 30px;
38 | top: 0; left: 0;
39 | border-radius: 15px;
40 | background: white;
41 | box-shadow: inset 0 0 0 1px rgba(0,0,0,.2), 0 2px 4px rgba(0,0,0,.2);
42 | transition: .25s ease-in-out;
43 | }
44 |
45 | .toggle:checked + label:before {
46 | width: 50px;
47 | background: rgba(19,191,17,1);
48 | }
49 |
50 | .toggle:checked + label:after {
51 | left: 20px;
52 | box-shadow: inset 0 0 0 1px rgba(19,191,17,1), 0 2px 4px rgba(0,0,0,.2);
53 | }
54 |
55 | .toggleDiscussion, .toggleMedia {
56 |
57 | .toggle + label {
58 | background-color: #E4E4E4;
59 | ;
60 | }
61 |
62 | .toggle + label:before {
63 | background: #E4E4E4;
64 | }
65 |
66 | .toggle:checked + label:after {
67 | left: 20px;
68 | box-shadow: inset 0 0 0 1px rgb(180, 191, 180), 0 2px 4px rgba(0,0,0,.2);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/css/base/modules/_view.scss:
--------------------------------------------------------------------------------
1 | .view-form {
2 | max-width: 690px;
3 | padding-top: 80px;
4 | padding-bottom: 80px;
5 | margin-left: auto;
6 | margin-right: auto;
7 | }
--------------------------------------------------------------------------------
/src/css/themes/bjv.scss:
--------------------------------------------------------------------------------
1 | @import '../base/colors.scss';
2 |
3 | $base-color: #3f3f3f;
4 | $text-color: $black;
5 | $link-color: $dark-grey;
6 | $hover-color: $dark-grey;
7 |
8 | $bar-positive1: #49D02EB2;
9 | $bar-positive2: #49D02E80;
10 | $bar-positive3: #49D02E33;
11 | $bar-neutral: #D8D8D8;
12 | $bar-negative3: #FF751A33;
13 | $bar-negative2: #FF751A80;
14 | $bar-negative1: #FF751AB2;
15 |
16 | $font-family-1: Roboto, serif;
17 | $font-family-2: Roboto, sans-serif;
18 | $font-family-icons: FontAwesome;
19 |
20 | $label-rating-positive: $bar-positive1;
21 | $label-rating-negative: $bar-negative1;
22 | $rating-star-rated: $dark-grey;
23 | $rating-star-active: $base-color;
24 | $rating-star-base: #e3e3e3;
25 | $rating-average-label: $dark-grey;
26 | $argument-list-header-text-transform: uppercase;
27 | $argument-list-header-color: black;
28 | $barometer-background-color: $light-grey;
29 | $barometer-bottom-splitter-color: $dark-grey;
30 |
31 | $discussion-title-color: $text-color;
32 | $list-entry-title-color: $text-color;
33 |
34 | @import '../base/main.scss';
35 |
--------------------------------------------------------------------------------
/src/css/themes/brabbl.scss:
--------------------------------------------------------------------------------
1 | @import '../base/colors.scss';
2 |
3 | $base-color: green;
4 | $text-color: $black;
5 | $link-color: $dark-grey;
6 | $hover-color: darken($base-color, 20%);
7 |
8 | $bar-positive1: #49D02EB2;
9 | $bar-positive2: #49D02E80;
10 | $bar-positive3: #49D02E33;
11 | $bar-neutral: #D8D8D8;
12 | $bar-negative3: #FF751A33;
13 | $bar-negative2: #FF751A80;
14 | $bar-negative1: #FF751AB2;
15 |
16 | $font-family-1: Georgia, serif;
17 | $font-family-2: Arial, sans-serif;
18 | $font-family-icons: FontAwesome;
19 |
20 | $label-rating-positive: $bar-positive1;
21 | $label-rating-negative: $bar-negative1;
22 | $rating-star-rated: $dark-grey;
23 | $rating-star-active: $base-color;
24 | $rating-star-base: #e3e3e3;
25 | $rating-average-label: $dark-grey;
26 | $argument-list-header-text-transform: uppercase;
27 | $argument-list-header-color: black;
28 | $barometer-background-color: $light-grey;
29 | $barometer-bottom-splitter-color: $dark-grey;
30 |
31 | $discussion-title-color: $text-color;
32 | $list-entry-title-color: $text-color;
33 |
34 | @import '../base/main.scss';
35 |
--------------------------------------------------------------------------------
/src/css/themes/brabbl2021.scss:
--------------------------------------------------------------------------------
1 | @import '../base/colors.scss';
2 |
3 | $base-color: #1E4E8A;
4 | $text-color: $black;
5 | $link-color: $dark-grey;
6 | $hover-color: darken($base-color, 20%);
7 |
8 | $bar-positive1: #49D02EB2;
9 | $bar-positive2: #49D02E80;
10 | $bar-positive3: #49D02E33;
11 | $bar-neutral: #D8D8D8;
12 | $bar-negative3: #FF751A33;
13 | $bar-negative2: #FF751A80;
14 | $bar-negative1: #FF751AB2;
15 |
16 | $font-family-1: Helvetica, sans-serif;
17 | $font-family-2: Helvetica, sans-serif;
18 | $font-family-icons: FontAwesome;
19 |
20 | $label-rating-positive: $bar-positive1;
21 | $label-rating-negative: $bar-negative1;
22 | $rating-star-rated: $dark-grey;
23 | $rating-star-active: $base-color;
24 | $rating-star-base: #e3e3e3;
25 | $rating-average-label: $dark-grey;
26 | $argument-list-header-text-transform: uppercase;
27 | $argument-list-header-color: $black;
28 | $barometer-background-color: $light-grey;
29 | $barometer-bottom-splitter-color: $dark-grey;
30 |
31 | $discussion-title-color: $text-color;
32 | $list-entry-title-color: $text-color;
33 |
34 | @import '../base/main.scss';
35 |
36 | #brabbl-widget,
37 | .ReactModal__Overlay {
38 | h1 {
39 | line-height: 16px;
40 | }
41 | .fullform-body select {
42 | padding: 5px !important;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/css/themes/eyp.scss:
--------------------------------------------------------------------------------
1 | @import '../base/colors.scss';
2 |
3 | $base-color: #1E4E8A;
4 | $text-color: $black;
5 | $link-color: $dark-grey;
6 | $hover-color: darken($base-color, 20%);
7 |
8 | $bar-positive1: #49D02EB2;
9 | $bar-positive2: #49D02E80;
10 | $bar-positive3: #49D02E33;
11 | $bar-neutral: #D8D8D8;
12 | $bar-negative3: #FF751A33;
13 | $bar-negative2: #FF751A80;
14 | $bar-negative1: #FF751AB2;
15 |
16 | $font-family-1: Roboto, sans-serif;
17 | $font-family-2: Roboto, sans-serif;
18 | $font-family-icons: FontAwesome;
19 |
20 | $label-rating-positive: $bar-positive1;
21 | $label-rating-negative: $bar-negative1;
22 | $rating-star-rated: $dark-grey;
23 | $rating-star-active: $base-color;
24 | $rating-star-base: #e3e3e3;
25 | $rating-average-label: $dark-grey;
26 | $argument-list-header-text-transform: uppercase;
27 | $argument-list-header-color: $black;
28 | $barometer-background-color: $light-grey;
29 | $barometer-bottom-splitter-color: $dark-grey;
30 |
31 | $discussion-title-color: $text-color;
32 | $list-entry-title-color: $text-color;
33 |
34 | @import '../base/main.scss';
35 |
36 | #brabbl-widget,
37 | .ReactModal__Overlay {
38 | h1 {
39 | line-height: 16px;
40 | }
41 | .fullform-body select {
42 | padding: 5px !important;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/css/themes/jugendinfo.scss:
--------------------------------------------------------------------------------
1 | @import '../base/colors.scss';
2 |
3 | $base-color: #05636C;
4 | $text-color: darken($base-color, 98%);
5 | $link-color: $dark-grey;
6 | $hover-color: $dark-grey;
7 |
8 | $bar-positive1: #49D02EB2;
9 | $bar-positive2: #49D02E80;
10 | $bar-positive3: #49D02E33;
11 | $bar-neutral: #D8D8D8;
12 | $bar-negative3: #FF751A33;
13 | $bar-negative2: #FF751A80;
14 | $bar-negative1: #FF751AB2;
15 |
16 | $font-family-1: Tahoma, Calibri, Verdana, Arial, sans-serif;
17 | $font-family-2: Tahoma, Calibri, Verdana, Arial, sans-serif;
18 | $font-family-icons: FontAwesome;
19 |
20 | $label-rating-positive: $bar-positive1;
21 | $label-rating-negative: $bar-negative1;
22 | $rating-star-rated: $base-color;
23 | $rating-star-active: $hover-color;
24 | $rating-star-base: #e3e3e3;
25 | $rating-average-label: $base-color;
26 | $argument-list-header-text-transform: None;
27 | $argument-list-header-color: $dark-grey;
28 | $barometer-background-color: $medium-grey;
29 | $barometer-bottom-splitter-color: $dark-grey;
30 |
31 | $discussion-title-color: $text-color;
32 | $list-entry-title-color: $text-color;
33 |
34 | @import '../base/main.scss';
35 |
--------------------------------------------------------------------------------
/src/css/themes/main.scss:
--------------------------------------------------------------------------------
1 | .spinner {
2 | text-align: center;
3 | position: relative;
4 | top: 20px;
5 | font-size: 60px;
6 | line-height: 16.8px;
7 | color: #EAEAEA;
8 | }
9 |
--------------------------------------------------------------------------------
/src/css/themes/vorwaerts.scss:
--------------------------------------------------------------------------------
1 | @import '../base/colors.scss';
2 |
3 | $base-color: $red;
4 | $text-color: $black;
5 | $link-color: $dark-grey;
6 | $hover-color: darken($base-color, 20%);
7 |
8 | $bar-positive1: #49D02EB2;
9 | $bar-positive2: #49D02E80;
10 | $bar-positive3: #49D02E33;
11 | $bar-neutral: #D8D8D8;
12 | $bar-negative3: #FF751A33;
13 | $bar-negative2: #FF751A80;
14 | $bar-negative1: #FF751AB2;
15 |
16 |
17 | $font-family-1: Georgia, serif;
18 | $font-family-2: Arial, sans-serif;
19 | $font-family-icons: FontAwesome;
20 |
21 | $label-rating-positive: $bar-positive1;
22 | $label-rating-negative: $bar-negative1;
23 | $rating-star-rated: $dark-grey;
24 | $rating-star-active: $base-color;
25 | $rating-star-base: #e3e3e3;
26 | $rating-average-label: $dark-grey;
27 | $argument-list-header-text-transform: uppercase;
28 | $argument-list-header-color: black;
29 | $barometer-background-color: $light-grey;
30 | $barometer-bottom-splitter-color: $dark-grey;
31 |
32 | $discussion-title-color: $text-color;
33 | $list-entry-title-color: $text-color;
34 |
35 | @import '../base/main.scss';
36 |
--------------------------------------------------------------------------------
/src/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/src/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/src/fonts/_animated.scss:
--------------------------------------------------------------------------------
1 | // Spinning Icons
2 | // --------------------------
3 |
4 | .#{$fa-css-prefix}-spin {
5 | -webkit-animation: fa-spin 2s infinite linear;
6 | animation: fa-spin 2s infinite linear;
7 | }
8 |
9 | .#{$fa-css-prefix}-pulse {
10 | -webkit-animation: fa-spin 1s infinite steps(8);
11 | animation: fa-spin 1s infinite steps(8);
12 | }
13 |
14 | @-webkit-keyframes fa-spin {
15 | 0% {
16 | -webkit-transform: rotate(0deg);
17 | transform: rotate(0deg);
18 | }
19 | 100% {
20 | -webkit-transform: rotate(359deg);
21 | transform: rotate(359deg);
22 | }
23 | }
24 |
25 | @keyframes fa-spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | transform: rotate(0deg);
29 | }
30 | 100% {
31 | -webkit-transform: rotate(359deg);
32 | transform: rotate(359deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/fonts/_bordered-pulled.scss:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em $fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .#{$fa-css-prefix}-pull-left { float: left; }
11 | .#{$fa-css-prefix}-pull-right { float: right; }
12 |
13 | .#{$fa-css-prefix} {
14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; }
15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; }
16 | }
17 |
18 | /* Deprecated as of 4.4.0 */
19 | .pull-right { float: right; }
20 | .pull-left { float: left; }
21 |
22 | .#{$fa-css-prefix} {
23 | &.pull-left { margin-right: .3em; }
24 | &.pull-right { margin-left: .3em; }
25 | }
26 |
--------------------------------------------------------------------------------
/src/fonts/_core.scss:
--------------------------------------------------------------------------------
1 | // Base Class Definition
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix} {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/fonts/_fixed-width.scss:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .#{$fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/src/fonts/_larger.scss:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .#{$fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .#{$fa-css-prefix}-2x { font-size: 2em; }
11 | .#{$fa-css-prefix}-3x { font-size: 3em; }
12 | .#{$fa-css-prefix}-4x { font-size: 4em; }
13 | .#{$fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/src/fonts/_list.scss:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: $fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .#{$fa-css-prefix}-li {
11 | position: absolute;
12 | left: -$fa-li-width;
13 | width: $fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.#{$fa-css-prefix}-lg {
17 | left: -$fa-li-width + (4em / 14);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/fonts/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | @mixin fa-icon() {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | @mixin fa-icon-rotate($degrees, $rotation) {
15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
16 | -webkit-transform: rotate($degrees);
17 | -ms-transform: rotate($degrees);
18 | transform: rotate($degrees);
19 | }
20 |
21 | @mixin fa-icon-flip($horiz, $vert, $rotation) {
22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
23 | -webkit-transform: scale($horiz, $vert);
24 | -ms-transform: scale($horiz, $vert);
25 | transform: scale($horiz, $vert);
26 | }
27 |
--------------------------------------------------------------------------------
/src/fonts/_path.scss:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
--------------------------------------------------------------------------------
/src/fonts/_rotated-flipped.scss:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); }
5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); }
6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); }
7 |
8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); }
9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); }
10 |
11 | // Hook for IE8-9
12 | // -------------------------
13 |
14 | :root .#{$fa-css-prefix}-rotate-90,
15 | :root .#{$fa-css-prefix}-rotate-180,
16 | :root .#{$fa-css-prefix}-rotate-270,
17 | :root .#{$fa-css-prefix}-flip-horizontal,
18 | :root .#{$fa-css-prefix}-flip-vertical {
19 | filter: none;
20 | }
21 |
--------------------------------------------------------------------------------
/src/fonts/_stacked.scss:
--------------------------------------------------------------------------------
1 | // Stacked Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-stack {
5 | position: relative;
6 | display: inline-block;
7 | width: 2em;
8 | height: 2em;
9 | line-height: 2em;
10 | vertical-align: middle;
11 | }
12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x {
13 | position: absolute;
14 | left: 0;
15 | width: 100%;
16 | text-align: center;
17 | }
18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; }
19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; }
20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; }
21 |
--------------------------------------------------------------------------------
/src/fonts/font-awesome.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables";
7 | @import "mixins";
8 | @import "path";
9 | @import "core";
10 | @import "larger";
11 | @import "fixed-width";
12 | @import "list";
13 | @import "bordered-pulled";
14 | @import "animated";
15 | @import "rotated-flipped";
16 | @import "stacked";
17 | @import "icons";
18 |
--------------------------------------------------------------------------------
/src/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/src/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/src/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/src/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/src/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/src/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/src/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/src/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/src/img/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/src/img/.keep
--------------------------------------------------------------------------------
/src/img/Checkmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/src/img/Checkmark.png
--------------------------------------------------------------------------------
/src/img/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redblame315/brabbl-frontend/100f4b761f450273395c62ccfb033123b80f5ae4/src/img/arrow.png
--------------------------------------------------------------------------------
/src/js/actions/async.js:
--------------------------------------------------------------------------------
1 | import API from '../api';
2 | import { undiscussion_list } from '../reducers/discussion';
3 | import { processBootstrap, processDiscussionList, processUnDiscussionList, updateDiscussion, showArgument,
4 | fetchTrans, loading, setShouldSkipUpdate, setAutoUpdateLoading } from './app';
5 |
6 |
7 | export function bootstrapApp(articleId=null, isSilent=false) {
8 | return (dispatch, getState) => {
9 | if(!isSilent) {
10 | dispatch(loading(true))
11 | }
12 | dispatch(setAutoUpdateLoading(true))
13 | console.log("bootstrap");
14 | API.bootstrap(articleId).then(resp => {
15 | console.log(resp);
16 | const { shouldSkipUpdate } = getState().app;
17 | if(!shouldSkipUpdate) {
18 | dispatch(processBootstrap(resp))
19 | } else {
20 | dispatch(setShouldSkipUpdate(false))
21 | }
22 |
23 | if(articleId != null)
24 | API.get_undiscussion_list().then(resp=>{dispatch(processUnDiscussionList(resp))})
25 | })
26 | }
27 | }
28 |
29 | export function getDiscussionList(url) {
30 | return dispatch => API.get_discussion_list(url).then(resp => dispatch(processDiscussionList(resp)));
31 | }
32 |
33 | export function getUnDiscussionList() {
34 | return dispatch => API.get_undiscussion_list().then(resp => dispatch(processUnDiscussionList(resp)));
35 | }
36 |
37 | export function reloadDiscussion(articleId) {
38 | return (dispatch, getState) =>
39 | API.get_discussion(articleId || window.brabbl.articleId)
40 | .then(resp => {
41 | console.log("reaload_discussion");
42 | const { isAutoUpdateFetchInProgress } = getState().app;
43 | if(isAutoUpdateFetchInProgress) {
44 | dispatch(setShouldSkipUpdate(true))
45 | }
46 | dispatch(updateDiscussion(resp))
47 | // console.log("call_get_undiscussion");
48 | // return API.get_undiscussion_list();
49 | });
50 | // .then(resp=>{
51 | // console.log("undiscussion_list_resp");
52 | // console.log(resp);
53 | // dispatch(undiscussion_list(resp.undiscussion_list))
54 | // });
55 | }
56 |
57 | export function fetchArgument(id) {
58 | return dispatch => API.get_argument(id).then(resp => dispatch(showArgument(resp)));
59 | }
60 |
61 | export function loadTrans() {
62 | return dispatch => API.get_trans().then(resp => dispatch(fetchTrans(resp)));
63 | }
--------------------------------------------------------------------------------
/src/js/components/__tests__/app.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount, render } from 'enzyme';
3 | import App from '../app';
4 | import configureStore from 'redux-mock-store';
5 | import { Provider } from 'react-redux';
6 | const mockStore = configureStore([]);
7 | import { initialState } from '../__mocks__/app.mock';
8 |
9 | describe("App View", () => {
10 | test('renders the spinner if no discussion(s) could be fetched', () => {
11 | initialState.app.view = 'detail';
12 | initialState.app.loading = true;
13 | const store = mockStore(initialState);
14 |
15 | const wrapper = render(
16 |
17 |
18 | );
19 | expect(wrapper.find('.fa-spinner')).toHaveLength(1)
20 | });
21 |
22 | test('renders the list-view if requested', () => {
23 | let state = { ...initialState };
24 | state.app = { ...state.app, view: 'list', loading: false };
25 | const store = mockStore(state);
26 |
27 | const wrapper = render(
28 |
29 |
30 | );
31 | expect(wrapper.hasClass('discussion-list-widget')).toEqual(true)
32 | });
33 | })
--------------------------------------------------------------------------------
/src/js/components/argument/index.js:
--------------------------------------------------------------------------------
1 | import ArgumentButtons from './argument-buttons';
2 | import ArgumentList from './argument-list';
3 | import Argument from './argument';
4 | import StarRating from './starrating';
5 |
6 | export { ArgumentButtons, ArgumentList, Argument, StarRating };
7 |
--------------------------------------------------------------------------------
/src/js/components/avatar-container.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Avatar from 'react-avatar';
4 | import moment from 'moment';
5 |
6 | class AvatarContainer extends React.Component {
7 |
8 | render() {
9 | let user = this.props.user;
10 | if (!user) return null;
11 | let avatar_props = {
12 | src: user.image ? user.image.small : null,
13 | name: user.display_name,
14 | size: this.props.size || 38,
15 | className: 'avatar',
16 | };
17 | let hideInfo = this.props.hide_info
18 | let dateonly = this.props.dateonly
19 | if (user.linked && user.linked.length > 0 && !user.image.small) {
20 | let social_props = user.linked[0];
21 | avatar_props[social_props[0].replace('-oauth2', '') + 'Id'] = social_props[2];
22 | }
23 | let formatedDate = moment(this.props.date).format(this.props.dateFormat || "DD.MM.YYYY");
24 | let containerStyle = "avatar-container " + (this.props.containerStyle || "")
25 | return (
26 |
27 | {
28 | !dateonly &&
29 |
33 | }
34 |
35 | {
36 | !hideInfo &&
37 |
38 | {
39 | !dateonly &&
40 | {user.display_name}
41 | }
42 | {
43 | this.props.date &&
44 |
45 | {formatedDate}
46 |
47 | }
48 |
49 | }
50 |
51 | );
52 | }
53 | }
54 |
55 | AvatarContainer.propTypes = {
56 | user: PropTypes.object,
57 | size: PropTypes.number,
58 | date: PropTypes.string,
59 | dateFormat: PropTypes.string
60 | };
61 |
62 | export default AvatarContainer;
63 |
--------------------------------------------------------------------------------
/src/js/components/common/atoms/CAutoHeightAnimator/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class CAutoHeightAnimator extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.prevHeight = 0;
8 | }
9 | componentDidMount() {
10 | let elWrap = document.getElementById(this.props.divToAnimate)
11 | let wrect = elWrap.getBoundingClientRect();
12 | this.prevHeight = wrect.height + 10;
13 | }
14 |
15 | animate = () => {
16 | // get the current height
17 | let eAnimator = document.getElementById(this.props.id)
18 | let elWrap = document.getElementById(this.props.divToAnimate)
19 | let prevHeight = this.prevHeight
20 |
21 | // Set height:auto and get the new height
22 | let rect = elWrap.getBoundingClientRect();
23 | let newHeight = rect.height + 10;
24 |
25 | eAnimator.style.height = prevHeight + 'px'
26 |
27 | // Start Animation in next Animation frame from original height to new height
28 | // Reveal the contents via opacity animation
29 | requestAnimationFrame(() => {
30 | eAnimator.style.height = newHeight + 'px'
31 | eAnimator.style.transitionDelay = this.props.transitionDelay
32 | })
33 | setTimeout(function() { eAnimator.style.height = 'auto'; }, 500);
34 | this.prevHeight = newHeight
35 | }
36 | render () {
37 | return
38 | {this.props.children}
39 |
40 |
41 | }
42 | }
43 |
44 | export default CAutoHeightAnimator
--------------------------------------------------------------------------------
/src/js/components/common/atoms/CButton/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class CButton extends Component {
5 | onClick = () => {
6 | if(!this.props.isLoading) {
7 | if(this.props.onClick) this.props.onClick();
8 | if(this.props.isFilePicker) {
9 | this.upload.click();
10 | }
11 | }
12 |
13 | }
14 | onFileChange = (e) => {
15 | if(this.props.onFileChange) {
16 | this.props.onFileChange(e);
17 | }
18 | }
19 | getClassByType=(type) => {
20 | if(type == 'delete')
21 | return 'cw-button-container-delete'
22 | if(type == 'cancel')
23 | return 'cw-button-container-cancel'
24 | if(type == 'secondary')
25 | return 'cw-button-container-secondary'
26 | return 'cw-button-container'
27 | }
28 | render () {
29 | const props = this.props;
30 | return {this.onClick()}} type="button"
31 | className={this.getClassByType(this.props.type)}
32 | style={props.containerStyle} disabled={this.props.disabled}>
33 | {this.props.isLoading ? Loading
: props.children}
34 | {
35 | props.isFilePicker &&
36 | this.upload = ref} style={{ display: 'none' }}
37 | accept={this.props.fileAccept} onChange={e => { this.onFileChange(e)}}/>
38 | }
39 |
40 | }
41 | }
42 |
43 | CButton.propTypes = {
44 | isFilePicker: PropTypes.bool,
45 | fileAccept: PropTypes.string,
46 | onClick: PropTypes.func,
47 | onFileChange: PropTypes.func,
48 | isLoading: PropTypes.func
49 | };
50 |
51 |
52 | export default CButton
--------------------------------------------------------------------------------
/src/js/components/common/atoms/CCircularProgress/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | function CircularProgressWithLabel(props) {
5 | return (
6 |
7 | {Math.round(props.value)}%
8 |
9 | );
10 | }
11 |
12 | CircularProgressWithLabel.propTypes = {
13 | /**
14 | * The value of the progress indicator for the determinate variant.
15 | * Value between 0 and 100.
16 | */
17 | value: PropTypes.number.isRequired,
18 | };
19 |
20 | export default CircularProgressWithLabel
--------------------------------------------------------------------------------
/src/js/components/common/atoms/CCollapsable/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class CCollapsable extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | collapsed: !props.opened
9 | }
10 | }
11 | onCollapse = () => {
12 | this.setState({collapsed: !this.state.collapsed})
13 | }
14 | render () {
15 | const props = this.props;
16 | return
29 |
30 | }
31 | }
32 |
33 | CCollapsable.propTypes = {
34 | title: PropTypes.string,
35 | opened: PropTypes.bool
36 | };
37 |
38 |
39 | export default CCollapsable
--------------------------------------------------------------------------------
/src/js/components/common/atoms/CImageLoader/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const _loaded = {};
4 |
5 | class CImageLoader extends React.Component {
6 |
7 | //initial state: image loaded stage
8 | state = {
9 | loaded: _loaded[this.props.src]
10 | };
11 |
12 | //define our loading and loaded image classes
13 | static defaultProps = {
14 | className: "",
15 | loadingClassName: "img-loading",
16 | loadedClassName: "img-loaded"
17 | };
18 |
19 | //image onLoad handler to update state to loaded
20 | onLoad = () => {
21 | _loaded[this.props.src] = true;
22 | this.setState(() => ({ loaded: true }));
23 | };
24 |
25 |
26 | render() {
27 |
28 | let { className, loadedClassName, loadingClassName, ...props } = this.props;
29 |
30 | className = `${className} ${this.state.loaded
31 | ? loadedClassName
32 | : loadingClassName}`;
33 |
34 | return ;
39 | }
40 | }
41 |
42 | export default CImageLoader;
--------------------------------------------------------------------------------
/src/js/components/common/atoms/CMarkupParser/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types'
3 | import i18next from 'i18next';
4 |
5 | class CMarkupParser extends Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | renderParagraphs = () => {
11 | let html = this.props.htmlString;
12 | if (!html) return [];
13 | let paragraphs = html.split("\n\n");
14 | return paragraphs.map(paragraph => {
15 | return this.renderParagraph(paragraph)
16 | })
17 | }
18 | renderParagraph = (paragraph) => {
19 | return {this.renderTexts(paragraph)}
20 | }
21 | renderBoldText = (text) => {
22 | return {this.renderNormalText(text)}
23 | }
24 | renderNormalText = (text) => {
25 | if(!text) return []
26 | let normalTexts = text.split('\n')
27 | if(normalTexts.length > 1) {
28 | return normalTexts.map((normalText, index) => {
29 | if(normalTexts.length > 1 && index < normalTexts.length - 1) {
30 | return {normalText}
31 | } else {
32 | return normalText
33 | }
34 | })
35 | } else {
36 | return text
37 | }
38 |
39 | }
40 | renderTexts = (paragraph) => {
41 | let texts = paragraph.split("**");
42 | let boldTexts = texts.map((text, index) => {
43 | if (index > 0 && index < texts.length - 1) {
44 | return this.renderBoldText(text)
45 | } else {
46 | return this.renderNormalText(text)
47 | }
48 | })
49 | return boldTexts
50 | }
51 |
52 | render (){
53 | return
54 | {this.renderParagraphs()}
55 |
56 | }
57 | }
58 |
59 | CMarkupParser.propTypes = {
60 | htmlString: PropTypes.string
61 | }
62 |
63 | export default CMarkupParser
--------------------------------------------------------------------------------
/src/js/components/common/atoms/CPagination/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Pagination from 'rc-pagination'
3 | const localeInfo = {
4 |
5 | }
6 | class CPagination extends Component {
7 |
8 | render () {
9 | let prevIcon = {'<'}
10 | let nextIcon = {'>'}
11 | const props = this.props;
12 | return
19 |
20 | }
21 | }
22 |
23 | export default CPagination
--------------------------------------------------------------------------------
/src/js/components/common/atoms/index.js:
--------------------------------------------------------------------------------
1 | import CRemovableList from './CRemovableList'
2 | import CMarkupParser from './CMarkupParser'
3 | import CButton from './CButton'
4 | import CCircularProgress from './CCircularProgress'
5 | import CCollapsable from './CCollapsable'
6 | import CImageLoader from './CImageLoader'
7 | import CPagination from './CPagination'
8 | import CAutoHeightAnimator from './CAutoHeightAnimator'
9 |
10 | export {
11 | CRemovableList,
12 | CMarkupParser,
13 | CButton,
14 | CCircularProgress,
15 | CCollapsable,
16 | CImageLoader,
17 | CPagination,
18 | CAutoHeightAnimator
19 | }
--------------------------------------------------------------------------------
/src/js/components/common/custom-widgets/CKEditor.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import forms from '../newforms/newforms';
3 |
4 | var object = require('isomorph/object')
5 | var React = require('react')
6 | import { CKEditor } from '@ckeditor/ckeditor5-react';
7 | import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
8 |
9 | var Widget = forms.Widget
10 |
11 | var CKEditorWidget = Widget.extend({
12 | constructor: function CKEditor(kwargs) {
13 | if (!(this instanceof CKEditor)) { return new CKEditor(kwargs) }
14 | // Ensure we have something in attrs
15 | kwargs = object.extend({attrs: null}, kwargs)
16 | // Provide default 'cols' and 'rows' attributes
17 | kwargs.attrs = object.extend({rows: '3', cols: '40'}, kwargs.attrs)
18 | Widget.call(this, kwargs)
19 | }
20 | })
21 |
22 | CKEditorWidget.prototype.render = function(name, value, kwargs) {
23 | kwargs = object.extend({}, kwargs)
24 | if (value === null) {
25 | value = ''
26 | }
27 | var finalAttrs = this.buildAttrs(kwargs.attrs, {name: name})
28 | var valueAttr = 'data'
29 | finalAttrs[valueAttr] = value
30 | //return React.createElement('textarea', finalAttrs)
31 | return {
37 | editor.removeAttr("title");
38 | // You can store the "editor" and use when it is needed.
39 | } }
40 | {...finalAttrs}
41 | onChange={ ( event, editor ) => {
42 | const data = editor.getData();
43 | const e = {
44 | target: {
45 | name: finalAttrs.name,
46 | description: finalAttrs.description,
47 | getAttribute: (name) => {
48 | return null
49 | },
50 | value: data
51 | },
52 | }
53 | finalAttrs.onChange(e)
54 |
55 | } }
56 | onBlur={ ( event, editor ) => {
57 | } }
58 | onFocus={ ( event, editor ) => {
59 | } }
60 |
61 | />
62 | }
63 |
64 | export default CKEditorWidget
65 |
--------------------------------------------------------------------------------
/src/js/components/common/index.js:
--------------------------------------------------------------------------------
1 | import forms from './newforms/newforms';
2 |
3 | export { forms };
4 |
--------------------------------------------------------------------------------
/src/js/components/common/newforms/components/FormRow.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react')
4 |
5 | var BoundField = require('../BoundField')
6 | var ProgressMixin = require('./ProgressMixin')
7 | var PropTypes = require('prop-types')
8 |
9 | /**
10 | * Renders a "row" in a form. This can contain manually provided contents, or
11 | * if a BoundField is given, it will be used to display a field's label, widget,
12 | * error message(s), help text and async pending indicator.
13 | */
14 | class FormRow extends React.Component {
15 |
16 | constructor(props) {
17 | super(props);
18 | this.mixins = [ProgressMixin];
19 | }
20 |
21 |
22 | render() {
23 | var attrs = {}
24 | if (this.props.className) {
25 | attrs.className = this.props.className
26 | }
27 | if (this.props.hidden) {
28 | attrs.style = {display: 'none'}
29 | }
30 | // If content was given, use it
31 | if (this.props.content) {
32 | return {this.props.content}
33 | }
34 | // Otherwise render a BoundField
35 | var bf = this.props.bf
36 | var isPending = bf.isPending()
37 | return
38 | {bf.label && bf.labelTag()} {bf.render()}
39 | {isPending && ' '}
40 | {isPending && this.renderProgress()}
41 | {bf.errors().render()}
42 | {bf.helpText && ' '}
43 | {bf.helpTextTag()}
44 |
45 | }
46 | }
47 |
48 | FormRow.propTypes = {
49 | bf: PropTypes.instanceOf(BoundField)
50 | , className: PropTypes.string
51 | , component: PropTypes.any
52 | , content: PropTypes.any
53 | , hidden: PropTypes.bool
54 | , __all__(props) {
55 | if (!props.bf && !props.content) {
56 | return new Error(
57 | 'Invalid props supplied to `FormRow`, either `bf` or `content` ' +
58 | 'must be specified.'
59 | )
60 | }
61 | if (props.bf && props.content) {
62 | return new Error(
63 | 'Both `bf` and `content` props were passed to `FormRow` - `bf` ' +
64 | 'will be ignored.'
65 | )
66 | }
67 | }
68 | };
69 |
70 | FormRow.defaultProps = {
71 | component: 'div'
72 | }
73 |
74 | module.exports = FormRow
--------------------------------------------------------------------------------
/src/js/components/common/newforms/components/ProgressMixin.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 | var React = require('react')
5 | var PropTypes = require('prop-types')
6 |
7 | var ProgressMixin = {
8 | propTypes: {
9 | progress: PropTypes.any // Component or function to render async progress
10 | },
11 |
12 | renderProgress() {
13 | if (!this.props.progress) {
14 | return Validating...
15 | }
16 | if (is.Function(this.props.progress)) {
17 | return this.props.progress()
18 | }
19 | return
20 | }
21 | }
22 |
23 | module.exports = ProgressMixin
--------------------------------------------------------------------------------
/src/js/components/common/newforms/constants.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | /** Default maximum number of forms in a formset, to prevent memory exhaustion. */
5 | FORMSET_DEFAULT_MAX_NUM: 1000
6 | /** Default minimum number of forms in a formset. */
7 | , FORMSET_DEFAULT_MIN_NUM: 0
8 | /** Property under which non-field-specific errors are stored. */
9 | , NON_FIELD_ERRORS: '__all__'
10 | }
--------------------------------------------------------------------------------
/src/js/components/common/newforms/env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | browser: typeof window != 'undefined'
5 | }
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/BaseTemporalField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 | var object = require('isomorph/object')
5 | var time = require('isomorph/time')
6 |
7 | var formats = require('../formats')
8 | var locales = require('../locales')
9 |
10 | var Field = require('../Field')
11 |
12 | var {ValidationError} = require('validators')
13 | var {strip} = require('../util')
14 |
15 | /**
16 | * Base field for fields which validate that their input is a date or time.
17 | * @constructor
18 | * @extends {Field}
19 | * @param {Object=} kwargs
20 | */
21 | var BaseTemporalField = Field.extend({
22 | inputFormatType: ''
23 | , constructor: function BaseTemporalField(kwargs) {
24 | kwargs = object.extend({inputFormats: null}, kwargs)
25 | Field.call(this, kwargs)
26 | this.inputFormats = kwargs.inputFormats
27 | }
28 | })
29 |
30 | /**
31 | * Validates that its input is a valid date or time.
32 | * @param {(string|Date)} value user input.
33 | * @return {Date}
34 | * @throws {ValidationError} if the input is invalid.
35 | */
36 | BaseTemporalField.prototype.toJavaScript = function(value) {
37 | if (!is.Date(value)) {
38 | value = strip(value)
39 | }
40 | if (is.String(value)) {
41 | if (this.inputFormats === null) {
42 | this.inputFormats = formats.getFormat(this.inputFormatType)
43 | }
44 | for (var i = 0, l = this.inputFormats.length; i < l; i++) {
45 | try {
46 | return this.strpdate(value, this.inputFormats[i])
47 | }
48 | catch (e) {
49 | // pass
50 | }
51 | }
52 | }
53 | throw ValidationError(this.errorMessages.invalid, {code: 'invalid'})
54 | }
55 |
56 | /**
57 | * Creates a Date from the given input if it's valid based on a format.
58 | * @param {string} value
59 | * @param {string} format
60 | * @return {Date}
61 | */
62 | BaseTemporalField.prototype.strpdate = function(value, format) {
63 | return time.strpdate(value, format, locales.getDefaultLocale())
64 | }
65 |
66 | BaseTemporalField.prototype._hasChanged = function(initial, data) {
67 | try {
68 | data = this.toJavaScript(data)
69 | }
70 | catch (e) {
71 | if (!(e instanceof ValidationError)) { throw e }
72 | return true
73 | }
74 | initial = this.toJavaScript(initial)
75 | if (!!initial && !!data) {
76 | return initial.getTime() !== data.getTime()
77 | }
78 | else {
79 | return initial !== data
80 | }
81 | }
82 |
83 |
84 | module.exports = BaseTemporalField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/BooleanField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 |
5 | var CheckboxInput = require('../widgets/CheckboxInput')
6 | var Field = require('../Field')
7 |
8 | var {ValidationError} = require('validators')
9 |
10 | /**
11 | * Normalises its input to a Boolean primitive.
12 | * @constructor
13 | * @extends {Field}
14 | * @param {Object=} kwargs
15 | */
16 | var BooleanField = Field.extend({
17 | widget: CheckboxInput
18 |
19 | , constructor: function BooleanField(kwargs) {
20 | if (!(this instanceof BooleanField)) { return new BooleanField(kwargs) }
21 | Field.call(this, kwargs)
22 | }
23 | })
24 |
25 | BooleanField.prototype.toJavaScript = function(value) {
26 | // Explicitly check for a 'false' string, which is what a hidden field will
27 | // submit for false. Also check for '0', since this is what RadioSelect will
28 | // provide. Because Boolean('anything') == true, we don't need to handle that
29 | // explicitly.
30 | if (is.String(value) && (value.toLowerCase() == 'false' || value == '0')) {
31 | value = false
32 | }
33 | else {
34 | value = Boolean(value)
35 | }
36 | value = Field.prototype.toJavaScript.call(this, value)
37 | if (!value && this.required) {
38 | throw ValidationError(this.errorMessages.required, {code: 'required'})
39 | }
40 | return value
41 | }
42 |
43 | BooleanField.prototype._hasChanged = function(initial, data) {
44 | // Sometimes data or initial could be null or '' which should be the same
45 | // thing as false.
46 | if (initial === 'false') {
47 | // showHiddenInitial may have transformed false to 'false'
48 | initial = false
49 | }
50 | return (Boolean(initial) != Boolean(data))
51 | }
52 |
53 | module.exports = BooleanField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/CharField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var Field = require('../Field')
6 | var PasswordInput = require('../widgets/PasswordInput')
7 | var TextInput = require('../widgets/TextInput')
8 |
9 | var {MinLengthValidator, MaxLengthValidator} = require('validators')
10 |
11 | /**
12 | * Validates that its input is a valid String.
13 | * @constructor
14 | * @extends {Field}
15 | * @param {Object=} kwargs
16 | */
17 | var CharField = Field.extend({
18 | constructor: function CharField(kwargs) {
19 | if (!(this instanceof CharField)) { return new CharField(kwargs) }
20 | kwargs = object.extend({maxLength: null, minLength: null}, kwargs)
21 | this.maxLength = kwargs.maxLength
22 | this.minLength = kwargs.minLength
23 | Field.call(this, kwargs)
24 | if (this.minLength !== null) {
25 | this.validators.push(MinLengthValidator(this.minLength))
26 | }
27 | if (this.maxLength !== null) {
28 | this.validators.push(MaxLengthValidator(this.maxLength))
29 | }
30 | }
31 | })
32 |
33 | /**
34 | * @return {string}
35 | */
36 | CharField.prototype.toJavaScript = function(value) {
37 | if (this.isEmptyValue(value)) {
38 | return ''
39 | }
40 | return ''+value
41 | }
42 |
43 | /**
44 | * If this field is configured to enforce a maximum length, adds a suitable
45 | * maxLength attribute to text input fields.
46 | * @param {Widget} widget the widget being used to render this field's value.
47 | * @return {Object} additional attributes which should be added to the widget.
48 | */
49 | CharField.prototype.getWidgetAttrs = function(widget) {
50 | var attrs = Field.prototype.getWidgetAttrs.call(this, widget)
51 | if (this.maxLength !== null && (widget instanceof TextInput ||
52 | widget instanceof PasswordInput)) {
53 | attrs.maxLength = ''+this.maxLength
54 | }
55 | return attrs
56 | }
57 |
58 | module.exports = CharField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/ChoiceField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 | var object = require('isomorph/object')
5 |
6 | var Field = require('../Field')
7 | var Select = require('../widgets/Select')
8 |
9 | var {ValidationError} = require('validators')
10 | var {normaliseChoices} = require('../util')
11 |
12 | /**
13 | * Validates that its input is one of a valid list of choices.
14 | * @constructor
15 | * @extends {Field}
16 | * @param {Object=} kwargs
17 | */
18 | var ChoiceField = Field.extend({
19 | widget: Select
20 | , defaultErrorMessages: {
21 | invalidChoice: 'Select a valid choice. {value} is not one of the available choices.'
22 | }
23 |
24 | , constructor: function ChoiceField(kwargs) {
25 | if (!(this instanceof ChoiceField)) { return new ChoiceField(kwargs) }
26 | kwargs = object.extend({choices: []}, kwargs)
27 | Field.call(this, kwargs)
28 | this.setChoices(kwargs.choices)
29 | }
30 | })
31 |
32 | ChoiceField.prototype.choices = function() { return this._choices }
33 | ChoiceField.prototype.setChoices = function(choices) {
34 | // Setting choices also sets the choices on the widget
35 | this._choices = this.widget.choices = normaliseChoices(choices)
36 | }
37 |
38 | ChoiceField.prototype.toJavaScript = function(value) {
39 | if (this.isEmptyValue(value)) {
40 | return ''
41 | }
42 | return ''+value
43 | }
44 |
45 | /**
46 | * Validates that the given value is in this field's choices.
47 | */
48 | ChoiceField.prototype.validate = function(value) {
49 | Field.prototype.validate.call(this, value)
50 | if (value && !this.validValue(value)) {
51 | throw ValidationError(this.errorMessages.invalidChoice, {
52 | code: 'invalidChoice'
53 | , params: {value: value}
54 | })
55 | }
56 | }
57 |
58 | /**
59 | * Checks to see if the provided value is a valid choice.
60 | * @param {string} value the value to be validated.
61 | */
62 | ChoiceField.prototype.validValue = function(value) {
63 | var choices = this.choices()
64 | for (var i = 0, l = choices.length; i < l; i++) {
65 | if (is.Array(choices[i][1])) {
66 | // This is an optgroup, so look inside the group for options
67 | var optgroupChoices = choices[i][1]
68 | for (var j = 0, m = optgroupChoices.length; j < m; j++) {
69 | if (value === ''+optgroupChoices[j][0]) {
70 | return true
71 | }
72 | }
73 | }
74 | else if (value === ''+choices[i][0]) {
75 | return true
76 | }
77 | }
78 | return false
79 | }
80 |
81 | module.exports = ChoiceField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/ComboField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var Field = require('../Field')
6 |
7 | /**
8 | * A Field whose clean() method calls multiple Field clean() methods.
9 | * @constructor
10 | * @extends {Field}
11 | * @param {Object=} kwargs
12 | */
13 | var ComboField = Field.extend({
14 | constructor: function ComboField(kwargs) {
15 | if (!(this instanceof ComboField)) { return new ComboField(kwargs) }
16 | kwargs = object.extend({fields: []}, kwargs)
17 | Field.call(this, kwargs)
18 | // Set required to False on the individual fields, because the required
19 | // validation will be handled by ComboField, not by those individual fields.
20 | for (var i = 0, l = kwargs.fields.length; i < l; i++) {
21 | kwargs.fields[i].required = false
22 | }
23 | this.fields = kwargs.fields
24 | }
25 | })
26 |
27 | ComboField.prototype.clean = function(value) {
28 | Field.prototype.clean.call(this, value)
29 | for (var i = 0, l = this.fields.length; i < l; i++) {
30 | value = this.fields[i].clean(value)
31 | }
32 | return value
33 | }
34 |
35 | module.exports = ComboField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/DateField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var BaseTemporalField = require('./BaseTemporalField')
4 | var DateInput = require('../widgets/DateInput')
5 |
6 | /**
7 | * Validates that its input is a date.
8 | * @constructor
9 | * @extends {BaseTemporalField}
10 | * @param {Object=} kwargs
11 | */
12 | var DateField = BaseTemporalField.extend({
13 | widget: DateInput
14 | , inputFormatType: 'DATE_INPUT_FORMATS'
15 | , defaultErrorMessages: {
16 | invalid: 'Enter a valid date.'
17 | }
18 |
19 | , constructor: function DateField(kwargs) {
20 | if (!(this instanceof DateField)) { return new DateField(kwargs) }
21 | BaseTemporalField.call(this, kwargs)
22 | }
23 | })
24 |
25 | /**
26 | * Validates that the input can be converted to a date.
27 | * @param {?(string|Date)} value user input.
28 | * @return {?Date} a with its year, month and day attributes set, or null for
29 | * empty values when they are allowed.
30 | * @throws {ValidationError} if the input is invalid.
31 | */
32 | DateField.prototype.toJavaScript = function(value) {
33 | if (this.isEmptyValue(value)) {
34 | return null
35 | }
36 | if (value instanceof Date) {
37 | return new Date(value.getFullYear(), value.getMonth(), value.getDate())
38 | }
39 | return BaseTemporalField.prototype.toJavaScript.call(this, value)
40 | }
41 |
42 | module.exports = DateField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/DateTimeField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 |
5 | var BaseTemporalField = require('./BaseTemporalField')
6 | var DateTimeInput = require('../widgets/DateTimeInput')
7 |
8 | var {ValidationError} = require('validators')
9 |
10 | /**
11 | * Validates that its input is a date/time.
12 | * @constructor
13 | * @extends {BaseTemporalField}
14 | * @param {Object=} kwargs
15 | */
16 | var DateTimeField = BaseTemporalField.extend({
17 | widget: DateTimeInput
18 | , inputFormatType: 'DATETIME_INPUT_FORMATS'
19 | , defaultErrorMessages: {
20 | invalid: 'Enter a valid date/time.'
21 | }
22 |
23 | , constructor: function DateTimeField(kwargs) {
24 | if (!(this instanceof DateTimeField)) { return new DateTimeField(kwargs) }
25 | BaseTemporalField.call(this, kwargs)
26 | }
27 | })
28 |
29 | /**
30 | * @param {?(string|Date|Array.)} value user input.
31 | * @return {?Date}
32 | * @throws {ValidationError} if the input is invalid.
33 | */
34 | DateTimeField.prototype.toJavaScript = function(value) {
35 | if (this.isEmptyValue(value)) {
36 | return null
37 | }
38 | if (value instanceof Date) {
39 | return value
40 | }
41 | if (is.Array(value)) {
42 | // Input comes from a SplitDateTimeWidget, for example, so it's two
43 | // components: date and time.
44 | if (value.length != 2) {
45 | throw ValidationError(this.errorMessages.invalid, {code: 'invalid'})
46 | }
47 | if (this.isEmptyValue(value[0]) && this.isEmptyValue(value[1])) {
48 | return null
49 | }
50 | value = value.join(' ')
51 | }
52 | return BaseTemporalField.prototype.toJavaScript.call(this, value)
53 | }
54 |
55 |
56 | module.exports = DateTimeField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/EmailField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('../util')
4 |
5 | var CharField = require('./CharField')
6 | var EmailInput = require('../widgets/EmailInput')
7 |
8 | var {validateEmail} = require('validators')
9 |
10 | /**
11 | * Validates that its input appears to be a valid e-mail address.
12 | * @constructor
13 | * @extends {CharField}
14 | * @param {Object=} kwargs
15 | */
16 | var EmailField = CharField.extend({
17 | widget: EmailInput
18 | , defaultValidators: [validateEmail]
19 |
20 | , constructor: function EmailField(kwargs) {
21 | if (!(this instanceof EmailField)) { return new EmailField(kwargs) }
22 | CharField.call(this, kwargs)
23 | }
24 | })
25 |
26 | EmailField.prototype.clean = function(value) {
27 | value = util.strip(this.toJavaScript(value))
28 | return CharField.prototype.clean.call(this, value)
29 | }
30 |
31 |
32 | module.exports = EmailField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/FilePathField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var ChoiceField = require('./ChoiceField')
6 |
7 | /**
8 | * Allows choosing from files inside a certain directory.
9 | * @constructor
10 | * @extends {ChoiceField}
11 | * @param {string} path
12 | * @param {Object=} kwargs
13 | */
14 | var FilePathField = ChoiceField.extend({
15 | constructor: function FilePathField(path, kwargs) {
16 | if (!(this instanceof FilePathField)) { return new FilePathField(path, kwargs) }
17 | kwargs = object.extend({
18 | match: null, recursive: false, required: true, widget: null,
19 | label: null, initial: null, helpText: null,
20 | allowFiles: true, allowFolders: false
21 | }, kwargs)
22 |
23 | this.path = path
24 | this.match = object.pop(kwargs, 'match')
25 | this.recursive = object.pop(kwargs, 'recursive')
26 | this.allowFiles = object.pop(kwargs, 'allowFiles')
27 | this.allowFolders = object.pop(kwargs, 'allowFolders')
28 | delete kwargs.match
29 | delete kwargs.recursive
30 |
31 | kwargs.choices = []
32 | ChoiceField.call(this, kwargs)
33 |
34 | if (this.required) {
35 | this.setChoices([])
36 | }
37 | else {
38 | this.setChoices([['', '---------']])
39 | }
40 |
41 | if (this.match !== null) {
42 | this.matchRE = new RegExp(this.match)
43 | }
44 |
45 | // TODO Plug in file paths when running on the server
46 |
47 | this.widget.choices = this.choices()
48 | }
49 | })
50 |
51 | module.exports = FilePathField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/GenericIPAddressField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 | var validators = require('validators')
5 |
6 | var CharField = require('./CharField')
7 |
8 | var cleanIPv6Address = validators.ipv6.cleanIPv6Address
9 |
10 | var GenericIPAddressField = CharField.extend({
11 | constructor: function GenericIPAddressField(kwargs) {
12 | if (!(this instanceof GenericIPAddressField)) { return new GenericIPAddressField(kwargs) }
13 | kwargs = object.extend({protocol: 'both', unpackIPv4: false}, kwargs)
14 | this.unpackIPv4 = kwargs.unpackIPv4
15 | this.defaultValidators =
16 | validators.ipAddressValidators(kwargs.protocol, kwargs.unpackIPv4).validators
17 | CharField.call(this, kwargs)
18 | }
19 | })
20 |
21 | GenericIPAddressField.prototype.toJavaScript = function(value) {
22 | if (!value) {
23 | return ''
24 | }
25 | if (value && value.indexOf(':') != -1) {
26 | return cleanIPv6Address(value, {unpackIPv4: this.unpackIPv4})
27 | }
28 | return value
29 | }
30 |
31 |
32 | module.exports = GenericIPAddressField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/IPAddressField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var CharField = require('./CharField')
4 |
5 | var {validateIPv4Address} = require('validators')
6 |
7 | /**
8 | * Validates that its input is a valid IPv4 address.
9 | * @constructor
10 | * @extends {CharField}
11 | * @param {Object=} kwargs
12 | * @deprecated in favour of GenericIPAddressField
13 | */
14 | var IPAddressField = CharField.extend({
15 | defaultValidators: [validateIPv4Address]
16 |
17 | , constructor: function IPAddressField(kwargs) {
18 | if (!(this instanceof IPAddressField)) { return new IPAddressField(kwargs) }
19 | CharField.call(this, kwargs)
20 | }
21 | })
22 |
23 | module.exports = IPAddressField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/ImageField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var FileField = require('./FileField')
4 |
5 | /**
6 | * Validates that its input is a valid uploaded image.
7 | * @constructor
8 | * @extends {Field}
9 | * @param {Object=} kwargs
10 | */
11 | var ImageField = FileField.extend({
12 | defaultErrorMessages: {
13 | invalidImage: 'Upload a valid image. The file you uploaded was either not an image or a corrupted image.'
14 | }
15 |
16 | , constructor: function ImageField(kwargs) {
17 | if (!(this instanceof ImageField)) { return new ImageField(kwargs) }
18 | FileField.call(this, kwargs)
19 | }
20 | })
21 |
22 | /**
23 | * Checks that the file-upload field data contains a valid image.
24 | */
25 | ImageField.prototype.toJavaScript = function(data, initial) {
26 | var f = FileField.prototype.toJavaScript.call(this, data, initial)
27 | if (f === null) {
28 | return null
29 | }
30 |
31 | // TODO Plug in image processing code when running on the server
32 |
33 | return f
34 | }
35 |
36 | ImageField.prototype.getWidgetAttrs = function(widget) {
37 | var attrs = FileField.prototype.getWidgetAttrs.call(this, widget)
38 | attrs.accept = 'image/*'
39 | return attrs
40 | }
41 |
42 |
43 | module.exports = ImageField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/IntegerField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 | var Field = require('../Field')
5 | var NumberInput = require('../widgets/NumberInput')
6 |
7 | var {MaxValueValidator, MinValueValidator, ValidationError} = require('validators')
8 |
9 | /**
10 | * Validates that its input is a valid integer.
11 | * @constructor
12 | * @extends {Field}
13 | * @param {Object=} kwargs
14 | */
15 | var IntegerField = Field.extend({
16 | widget: NumberInput
17 | , defaultErrorMessages: {
18 | invalid: 'Enter a whole number.'
19 | }
20 |
21 | , constructor: function IntegerField(kwargs) {
22 | if (!(this instanceof IntegerField)) { return new IntegerField(kwargs) }
23 | kwargs = object.extend({maxValue: null, minValue: null}, kwargs)
24 | this.maxValue = kwargs.maxValue
25 | this.minValue = kwargs.minValue
26 | Field.call(this, kwargs)
27 |
28 | if (this.minValue !== null) {
29 | this.validators.push(MinValueValidator(this.minValue))
30 | }
31 | if (this.maxValue !== null) {
32 | this.validators.push(MaxValueValidator(this.maxValue))
33 | }
34 | }
35 | })
36 |
37 | /**
38 | * Validates that Number() can be called on the input with a result that isn't
39 | * NaN and doesn't contain any decimal points.
40 | * @param {*} value user input.
41 | * @return {?number} the result of Number(), or null for empty values.
42 | * @throws {ValidationError} if the input is invalid.
43 | */
44 | IntegerField.prototype.toJavaScript = function(value) {
45 | value = Field.prototype.toJavaScript.call(this, value)
46 | if (this.isEmptyValue(value)) {
47 | return null
48 | }
49 | value = Number(value)
50 | if (isNaN(value) || value.toString().indexOf('.') != -1) {
51 | throw ValidationError(this.errorMessages.invalid, {code: 'invalid'})
52 | }
53 | return value
54 | }
55 |
56 | IntegerField.prototype.getWidgetAttrs = function(widget) {
57 | var attrs = Field.prototype.getWidgetAttrs.call(this, widget)
58 | if (this.minValue !== null && !object.hasOwn(widget.attrs, 'min')) {
59 | object.setDefault(attrs, 'min', this.minValue)
60 | }
61 | if (this.maxValue !== null && !object.hasOwn(widget.attrs, 'max')) {
62 | object.setDefault(attrs, 'max', this.maxValue)
63 | }
64 | return attrs
65 | }
66 |
67 | module.exports = IntegerField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/NullBooleanField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var BooleanField = require('./BooleanField')
4 | var NullBooleanSelect = require('../widgets/NullBooleanSelect')
5 |
6 | /**
7 | * A field whose valid values are null, true and false.
8 | * Invalid values are cleaned to null.
9 | * @constructor
10 | * @extends {BooleanField}
11 | * @param {Object=} kwargs
12 | */
13 | var NullBooleanField = BooleanField.extend({
14 | widget: NullBooleanSelect
15 |
16 | , constructor: function NullBooleanField(kwargs) {
17 | if (!(this instanceof NullBooleanField)) { return new NullBooleanField(kwargs) }
18 | BooleanField.call(this, kwargs)
19 | }
20 | })
21 |
22 | NullBooleanField.prototype.toJavaScript = function(value) {
23 | // Explicitly checks for the string 'True' and 'False', which is what a
24 | // hidden field will submit for true and false, and for '1' and '0', which
25 | // is what a RadioField will submit. Unlike the BooleanField we also need
26 | // to check for true, because we are not using Boolean() function.
27 | if (value === true || value == 'True' || value == 'true' || value == '1') {
28 | return true
29 | }
30 | else if (value === false || value == 'False' || value == 'false' || value == '0') {
31 | return false
32 | }
33 | return null
34 | }
35 |
36 | NullBooleanField.prototype.validate = function(value) {}
37 |
38 | NullBooleanField.prototype._hasChanged = function(initial, data) {
39 | // null (unknown) and false (No) are not the same
40 | if (initial !== null) {
41 | initial = Boolean(initial)
42 | }
43 | if (data !== null) {
44 | data = Boolean(data)
45 | }
46 | return initial != data
47 | }
48 |
49 | module.exports = NullBooleanField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/RegexField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 |
5 | var CharField = require('./CharField')
6 |
7 | var {RegexValidator} = require('validators')
8 |
9 | /**
10 | * Validates that its input matches a given regular expression.
11 | * @constructor
12 | * @extends {CharField}
13 | * @param {(RegExp|string)} regex
14 | * @param {Object=} kwargs
15 | */
16 | var RegexField = CharField.extend({
17 | constructor: function RegexField(regex, kwargs) {
18 | if (!(this instanceof RegexField)) { return new RegexField(regex, kwargs) }
19 | CharField.call(this, kwargs)
20 | if (is.String(regex)) {
21 | regex = new RegExp(regex)
22 | }
23 | this.regex = regex
24 | this.validators.push(RegexValidator({regex: this.regex}))
25 | }
26 | })
27 |
28 | module.exports = RegexField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/SlugField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var validators = require('validators')
4 |
5 | var CharField = require('./CharField')
6 |
7 | var {strip} = require('../util')
8 |
9 | /**
10 | * Validates that its input is a valid slug.
11 | * @constructor
12 | * @extends {CharField}
13 | * @param {Object=} kwargs
14 | */
15 | var SlugField = CharField.extend({
16 | defaultValidators: [validators.validateSlug]
17 | , constructor: function SlugField(kwargs) {
18 | if (!(this instanceof SlugField)) { return new SlugField(kwargs) }
19 | CharField.call(this, kwargs)
20 | }
21 | })
22 |
23 | SlugField.prototype.clean = function(value) {
24 | value = strip(this.toJavaScript(value))
25 | return CharField.prototype.clean.call(this, value)
26 | }
27 |
28 | module.exports = SlugField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/TimeField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var time = require('isomorph/time')
4 |
5 | var locales = require('../locales')
6 |
7 | var BaseTemporalField = require('./BaseTemporalField')
8 | var TimeInput = require('../widgets/TimeInput')
9 |
10 | /**
11 | * Validates that its input is a time.
12 | * @constructor
13 | * @extends {BaseTemporalField}
14 | * @param {Object=} kwargs
15 | */
16 | var TimeField = BaseTemporalField.extend({
17 | widget: TimeInput
18 | , inputFormatType: 'TIME_INPUT_FORMATS'
19 | , defaultErrorMessages: {
20 | invalid: 'Enter a valid time.'
21 | }
22 |
23 | , constructor: function TimeField(kwargs) {
24 | if (!(this instanceof TimeField)) { return new TimeField(kwargs) }
25 | BaseTemporalField.call(this, kwargs)
26 | }
27 | })
28 |
29 | /**
30 | * Validates that the input can be converted to a time.
31 | * @param {?(string|Date)} value user input.
32 | * @return {?Date} a Date with its hour, minute and second attributes set, or
33 | * null for empty values when they are allowed.
34 | * @throws {ValidationError} if the input is invalid.
35 | */
36 | TimeField.prototype.toJavaScript = function(value) {
37 | if (this.isEmptyValue(value)) {
38 | return null
39 | }
40 | if (value instanceof Date) {
41 | return new Date(1900, 0, 1, value.getHours(), value.getMinutes(), value.getSeconds())
42 | }
43 | return BaseTemporalField.prototype.toJavaScript.call(this, value)
44 | }
45 |
46 | /**
47 | * Creates a Date representing a time from the given input if it's valid based
48 | * on the format.
49 | * @param {string} value
50 | * @param {string} format
51 | * @return {Date}
52 | */
53 | TimeField.prototype.strpdate = function(value, format) {
54 | var t = time.strptime(value, format, locales.getDefaultLocale())
55 | return new Date(1900, 0, 1, t[3], t[4], t[5])
56 | }
57 |
58 | module.exports = TimeField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/TypedChoiceField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var ChoiceField = require('./ChoiceField')
6 |
7 | var {ValidationError} = require('validators')
8 |
9 | /**
10 | * A ChoiceField which returns a value coerced by some provided function.
11 | * @constructor
12 | * @extends {ChoiceField}
13 | * @param {Object=} kwargs
14 | */
15 | var TypedChoiceField = ChoiceField.extend({
16 | constructor: function TypedChoiceField(kwargs) {
17 | if (!(this instanceof TypedChoiceField)) { return new TypedChoiceField(kwargs) }
18 | kwargs = object.extend({
19 | coerce: function(val) { return val }, emptyValue: ''
20 | }, kwargs)
21 | this.coerce = object.pop(kwargs, 'coerce')
22 | this.emptyValue = object.pop(kwargs, 'emptyValue')
23 | ChoiceField.call(this, kwargs)
24 | }
25 | })
26 |
27 | /**
28 | * Validate that the value can be coerced to the right type (if not empty).
29 | */
30 | TypedChoiceField.prototype._coerce = function(value) {
31 | if (value === this.emptyValue || this.isEmptyValue(value)) {
32 | return this.emptyValue
33 | }
34 | try {
35 | value = this.coerce(value)
36 | }
37 | catch (e) {
38 | throw ValidationError(this.errorMessages.invalidChoice, {
39 | code: 'invalidChoice'
40 | , params: {value: value}
41 | })
42 | }
43 | return value
44 | }
45 |
46 | TypedChoiceField.prototype.clean = function(value) {
47 | value = ChoiceField.prototype.clean.call(this, value)
48 | return this._coerce(value)
49 | }
50 |
51 |
52 | module.exports = TypedChoiceField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/TypedMultipleChoiceField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 | var object = require('isomorph/object')
5 | var MultipleChoiceField = require('./MultipleChoiceField')
6 |
7 | var {ValidationError} = require('validators')
8 |
9 | /**
10 | * A MultipleChoiceField which returns values coerced by some provided function.
11 | * @constructor
12 | * @extends {MultipleChoiceField}
13 | * @param {Object=} kwargs
14 | */
15 | var TypedMultipleChoiceField = MultipleChoiceField.extend({
16 | constructor: function TypedMultipleChoiceField(kwargs) {
17 | if (!(this instanceof TypedMultipleChoiceField)) { return new TypedMultipleChoiceField(kwargs) }
18 | kwargs = object.extend({
19 | coerce: function(val) { return val }, emptyValue: []
20 | }, kwargs)
21 | this.coerce = object.pop(kwargs, 'coerce')
22 | this.emptyValue = object.pop(kwargs, 'emptyValue')
23 | MultipleChoiceField.call(this, kwargs)
24 | }
25 | })
26 |
27 | TypedMultipleChoiceField.prototype._coerce = function(value) {
28 | if (value === this.emptyValue || this.isEmptyValue(value) ||
29 | (is.Array(value) && !value.length)) {
30 | return this.emptyValue
31 | }
32 | var newValue = []
33 | for (var i = 0, l = value.length; i < l; i++) {
34 | try {
35 | newValue.push(this.coerce(value[i]))
36 | }
37 | catch (e) {
38 | throw ValidationError(this.errorMessages.invalidChoice, {
39 | code: 'invalidChoice'
40 | , params: {value: value[i]}
41 | })
42 | }
43 | }
44 | return newValue
45 | }
46 |
47 | TypedMultipleChoiceField.prototype.clean = function(value) {
48 | value = MultipleChoiceField.prototype.clean.call(this, value)
49 | return this._coerce(value)
50 | }
51 |
52 | TypedMultipleChoiceField.prototype.validate = function(value) {
53 | if (value !== this.emptyValue || (is.Array(value) && value.length)) {
54 | MultipleChoiceField.prototype.validate.call(this, value)
55 | }
56 | else if (this.required) {
57 | throw ValidationError(this.errorMessages.required, {code: 'required'})
58 | }
59 | }
60 |
61 | module.exports = TypedMultipleChoiceField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/fields/URLField.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var url = require('isomorph/url')
4 |
5 | var CharField = require('./CharField')
6 | var URLInput = require('../widgets/URLInput')
7 |
8 | var {URLValidator} = require('validators')
9 | var {strip} = require('../util')
10 |
11 | /**
12 | * Validates that its input appears to be a valid URL.
13 | * @constructor
14 | * @extends {CharField}
15 | * @param {Object=} kwargs
16 | */
17 | var URLField = CharField.extend({
18 | widget: URLInput
19 | , defaultErrorMessages: {
20 | invalid: 'Enter a valid URL.'
21 | }
22 | , defaultValidators: [URLValidator()]
23 |
24 | , constructor: function URLField(kwargs) {
25 | if (!(this instanceof URLField)) { return new URLField(kwargs) }
26 | CharField.call(this, kwargs)
27 | }
28 | })
29 |
30 | URLField.prototype.toJavaScript = function(value) {
31 | if (value) {
32 | var urlFields = url.parseUri(value)
33 | if (!urlFields.protocol) {
34 | // If no URL protocol given, assume http://
35 | urlFields.protocol = 'http'
36 | }
37 | if (!urlFields.path) {
38 | // The path portion may need to be added before query params
39 | urlFields.path = '/'
40 | }
41 | value = url.makeUri(urlFields)
42 | }
43 | return CharField.prototype.toJavaScript.call(this, value)
44 | }
45 |
46 | URLField.prototype.clean = function(value) {
47 | value = strip(this.toJavaScript(value))
48 | return CharField.prototype.clean.call(this, value)
49 | }
50 |
51 | module.exports = URLField
--------------------------------------------------------------------------------
/src/js/components/common/newforms/formats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var locales = require('./locales')
6 |
7 | /**
8 | * Standard input formats which will always be accepted.
9 | */
10 | var ISO_INPUT_FORMATS = {
11 | 'DATE_INPUT_FORMATS': ['%Y-%m-%d']
12 | , 'TIME_INPUT_FORMATS': ['%H:%M:%S', '%H:%M']
13 | , 'DATETIME_INPUT_FORMATS': [
14 | '%Y-%m-%d %H:%M:%S'
15 | , '%Y-%m-%d %H:%M'
16 | , '%Y-%m-%d'
17 | ]
18 | }
19 |
20 | var formatCache = {}
21 |
22 | /**
23 | * Gets all acceptable formats of a certain type (e.g. DATE_INPUT_FORMATS) for a
24 | * particular language code. All date/time formats will have the applicable ISO
25 | * formats added as lowest-precedence.
26 | * If an unknown language code is given, the default locale's formats will be
27 | * used instead.
28 | * If the locale doesn't have configuration for the format type, only the ISO
29 | * formats will be returned.
30 | * @param {string} formatType
31 | * @param {string=} lang language code - if not given, the default locale's
32 | * formats will be returned.
33 | * @return {Array.} a list of formats
34 | */
35 | function getFormat(formatType, lang) {
36 | if (!lang) {
37 | lang = locales.getDefaultLocale()
38 | }
39 | var cacheKey = formatType + ':' + lang
40 | if (!object.hasOwn(formatCache, cacheKey)) {
41 | var langLocales = locales.getLocales(lang)
42 | var localeFormats = []
43 | for (var i = 0, l = langLocales.length; i < l; i++) {
44 | var locale = langLocales[i]
45 | if (object.hasOwn(locale, formatType)) {
46 | // Copy locale-specific formats, as we may be adding to them
47 | localeFormats = locale[formatType].slice()
48 | break
49 | }
50 | }
51 | if (object.hasOwn(ISO_INPUT_FORMATS, formatType)) {
52 | var isoFormats = ISO_INPUT_FORMATS[formatType]
53 | for (var j = 0, m = isoFormats.length; j < m; j++) {
54 | var isoFormat = isoFormats[j]
55 | if (localeFormats.indexOf(isoFormat) == -1) {
56 | localeFormats.push(isoFormat)
57 | }
58 | }
59 | }
60 | formatCache[cacheKey] = localeFormats
61 | }
62 | return formatCache[cacheKey]
63 | }
64 |
65 | module.exports = {
66 | getFormat: getFormat
67 | }
68 |
--------------------------------------------------------------------------------
/src/js/components/common/newforms/forms/isFormAsync.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 |
5 | function isFormAsync(constructor) {
6 | var proto = constructor.prototype
7 | if (proto.clean.length == 1) { return true }
8 | var fieldNames = Object.keys(proto.baseFields)
9 | for (var i = 0, l = fieldNames.length; i < l ; i++) {
10 | var customClean = proto._getCustomClean(fieldNames[i])
11 | if (is.Function(customClean) && customClean.length == 1) {
12 | return true
13 | }
14 | }
15 | return false
16 | }
17 |
18 | module.exports = isFormAsync
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/CheckboxInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 | var object = require('isomorph/object')
5 | var React = require('react')
6 |
7 | var Widget = require('../Widget')
8 |
9 | function defaultCheckTest(value) {
10 | return (value !== false && value != null && value !== '')
11 | }
12 |
13 | /**
14 | * An HTML widget.
15 | * @constructor
16 | * @extends {Widget}
17 | * @param {Object=} kwargs
18 | */
19 | var CheckboxInput = Widget.extend({
20 | constructor: function CheckboxInput(kwargs) {
21 | if (!(this instanceof Widget)) { return new CheckboxInput(kwargs) }
22 | kwargs = object.extend({checkTest: defaultCheckTest}, kwargs)
23 | Widget.call(this, kwargs)
24 | this.checkTest = kwargs.checkTest
25 | }
26 | , validation: {onChange: true}
27 | })
28 |
29 | CheckboxInput.prototype.render = function(name, value, kwargs) {
30 | kwargs = object.extend({}, kwargs)
31 | var finalAttrs = this.buildAttrs(kwargs.attrs, {type: 'checkbox',
32 | name: name})
33 | if (value !== '' && value !== true && value !== false && value !== null &&
34 | value !== undefined) {
35 | // Only add the value attribute if value is non-empty
36 | finalAttrs.value = value
37 | }
38 | var checkedAttr = (kwargs.controlled ? 'checked' : 'defaultChecked')
39 | finalAttrs[checkedAttr] = this.checkTest(value)
40 | return React.createElement('input', finalAttrs)
41 | }
42 |
43 | CheckboxInput.prototype.valueFromData = function(data, files, name) {
44 | if (typeof data[name] == 'undefined') {
45 | // A missing value means False because HTML form submission does not
46 | // send results for unselected checkboxes.
47 | return false
48 | }
49 | var value = data[name]
50 | var values = {'true': true, 'false': false}
51 | // Translate true and false strings to boolean values
52 | if (is.String(value)) {
53 | value = object.get(values, value.toLowerCase(), value)
54 | }
55 | return !!value
56 | }
57 |
58 | module.exports = CheckboxInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/CheckboxSelectMultiple.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var CheckboxFieldRenderer = require('./renderers/CheckboxFieldRenderer')
4 | var RendererMixin = require('./renderers/RendererMixin')
5 | var SelectMultiple = require('./SelectMultiple')
6 |
7 | /**
8 | * Multiple selections represented as a list of widgets.
9 | * @constructor
10 | * @extends {SelectMultiple}
11 | * @param {Object=} kwargs
12 | */
13 | var CheckboxSelectMultiple = SelectMultiple.extend({
14 | __mixins__: [RendererMixin]
15 | , constructor: function(kwargs) {
16 | if (!(this instanceof CheckboxSelectMultiple)) { return new CheckboxSelectMultiple(kwargs) }
17 | RendererMixin.call(this, kwargs)
18 | SelectMultiple.call(this, kwargs)
19 | }
20 | , renderer: CheckboxFieldRenderer
21 | , _emptyValue: []
22 | })
23 |
24 | module.exports = CheckboxSelectMultiple
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/DateInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var DateTimeBaseInput = require('./DateTimeBaseInput')
4 |
5 | /**
6 | * @constructor
7 | * @extends {DateTimeBaseInput}
8 | * @param {Object=} kwargs
9 | */
10 | var DateInput = DateTimeBaseInput.extend({
11 | formatType: 'DATE_INPUT_FORMATS'
12 | , constructor: function DateInput(kwargs) {
13 | if (!(this instanceof DateInput)) { return new DateInput(kwargs) }
14 | DateTimeBaseInput.call(this, kwargs)
15 | }
16 | })
17 |
18 | module.exports = DateInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/DateTimeBaseInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 | var object = require('isomorph/object')
5 | var time = require('isomorph/time')
6 |
7 | var formats = require('../formats')
8 | var locales = require('../locales')
9 | var TextInput = require('./TextInput')
10 |
11 | /**
12 | * A which, if given a Date object to display, formats it as
13 | * an appropriate date/time String.
14 | * @constructor
15 | * @extends {TextInput}
16 | * @param {Object=} kwargs
17 | */
18 | var DateTimeBaseInput = TextInput.extend({
19 | formatType: ''
20 | , constructor: function DateTimeBaseInput(kwargs) {
21 | kwargs = object.extend({format: null}, kwargs)
22 | TextInput.call(this, kwargs)
23 | this.format = kwargs.format
24 | }
25 | })
26 |
27 | DateTimeBaseInput.prototype._formatValue = function(value) {
28 | if (is.Date(value)) {
29 | if (this.format === null) {
30 | this.format = formats.getFormat(this.formatType)[0]
31 | }
32 | return time.strftime(value, this.format, locales.getDefaultLocale())
33 | }
34 | return value
35 | }
36 |
37 | module.exports = DateTimeBaseInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/DateTimeInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var DateTimeBaseInput = require('./DateTimeBaseInput')
4 |
5 | /**
6 | * @constructor
7 | * @extends {DateTimeBaseInput}
8 | * @param {Object=} kwargs
9 | */
10 | var DateTimeInput = DateTimeBaseInput.extend({
11 | formatType: 'DATETIME_INPUT_FORMATS'
12 | , constructor: function DateTimeInput(kwargs) {
13 | if (!(this instanceof DateTimeInput)) { return new DateTimeInput(kwargs) }
14 | DateTimeBaseInput.call(this, kwargs)
15 | }
16 | })
17 |
18 | module.exports = DateTimeInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/EmailInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var TextInput = require('./TextInput')
4 |
5 | /**
6 | * An HTML widget.
7 | * @constructor
8 | * @extends {TextInput}
9 | * @param {Object=} kwargs
10 | */
11 | var EmailInput = TextInput.extend({
12 | constructor: function EmailInput(kwargs) {
13 | if (!(this instanceof EmailInput)) { return new EmailInput(kwargs) }
14 | TextInput.call(this, kwargs)
15 | }
16 | , inputType: 'email'
17 | })
18 |
19 | module.exports = EmailInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/FileInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var Input = require('./Input')
6 |
7 | var env =require('../env')
8 |
9 | /**
10 | * An HTML widget.
11 | * @constructor
12 | * @extends {Input}
13 | * @param {Object=} kwargs
14 | */
15 | var FileInput = Input.extend({
16 | constructor: function FileInput(kwargs) {
17 | if (!(this instanceof FileInput)) { return new FileInput(kwargs) }
18 | Input.call(this, kwargs)
19 | }
20 | , inputType: 'file'
21 | , needsMultipartForm: true
22 | , validation: {onChange: true}
23 | , isValueSettable: false
24 | })
25 |
26 | FileInput.prototype.render = function(name, value, kwargs) {
27 | return Input.prototype.render.call(this, name, null, kwargs)
28 | }
29 |
30 | /**
31 | * On the client, files will be populated with File objects from the input's
32 | * FileList when supported, otherwise its value will be in data as a fallback.
33 | */
34 | FileInput.prototype.valueFromData = function(data, files, name) {
35 | var dataSource = files
36 | if (env.browser && !(name in files)) {
37 | dataSource = data
38 | }
39 | return object.get(dataSource, name, null)
40 | }
41 |
42 | module.exports = FileInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/HiddenInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Input = require('./Input')
4 |
5 | /**
6 | * An HTML widget.
7 | * @constructor
8 | * @extends {Input}
9 | * @param {Object=} kwargs
10 | */
11 | var HiddenInput = Input.extend({
12 | constructor: function HiddenInput(kwargs) {
13 | if (!(this instanceof HiddenInput)) { return new HiddenInput(kwargs) }
14 | Input.call(this, kwargs)
15 | }
16 | , inputType: 'hidden'
17 | , isHidden: true
18 | })
19 |
20 | module.exports = HiddenInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/Input.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 | var React = require('react')
5 |
6 | var Widget = require('../Widget')
7 |
8 | /**
9 | * An HTML widget.
10 | * @constructor
11 | * @extends {Widget}
12 | * @param {Object=} kwargs
13 | */
14 | var Input = Widget.extend({
15 | constructor: function Input(kwargs) {
16 | if (!(this instanceof Input)) { return new Input(kwargs) }
17 | Widget.call(this, kwargs)
18 | }
19 | /** The type attribute of this input - subclasses must define it. */
20 | , inputType: null
21 | })
22 |
23 | Input.prototype._formatValue = function(value) {
24 | return value
25 | }
26 |
27 | Input.prototype.render = function(name, value, kwargs) {
28 | kwargs = object.extend({attrs: null}, kwargs)
29 | if (value === null) {
30 | value = ''
31 | }
32 | var finalAttrs = this.buildAttrs(kwargs.attrs, {type: this.inputType,
33 | name: name})
34 | // Hidden inputs can be made controlled inputs by default, as the user
35 | // can't directly interact with them.
36 | var valueAttr = (kwargs.controlled || this.isHidden ? 'value' : 'defaultValue')
37 | if (!(valueAttr == 'defaultValue' && value === '')) {
38 | finalAttrs[valueAttr] = (value !== '' ? ''+this._formatValue(value) : value)
39 | }
40 | return React.createElement('input', finalAttrs)
41 | }
42 |
43 | module.exports = Input
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/MultipleHiddenInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 | var React = require('react')
5 |
6 | var HiddenInput = require('./HiddenInput')
7 |
8 | /**
9 | * A widget that handles for fields that have a list of
10 | * values.
11 | * @constructor
12 | * @extends {HiddenInput}
13 | * @param {Object=} kwargs
14 | */
15 | var MultipleHiddenInput = HiddenInput.extend({
16 | constructor: function MultipleHiddenInput(kwargs) {
17 | if (!(this instanceof MultipleHiddenInput)) { return new MultipleHiddenInput(kwargs) }
18 | HiddenInput.call(this, kwargs)
19 | }
20 | })
21 |
22 | MultipleHiddenInput.prototype.render = function(name, value, kwargs) {
23 | kwargs = object.extend({attrs: null}, kwargs)
24 | if (value === null) {
25 | value = []
26 | }
27 | var finalAttrs = this.buildAttrs(kwargs.attrs, {type: this.inputType,
28 | name: name})
29 | var id = object.get(finalAttrs, 'id', null)
30 | var key = object.get(finalAttrs, 'key', null)
31 | var inputs = []
32 | for (var i = 0, l = value.length; i < l; i++) {
33 | var inputAttrs = object.extend({}, finalAttrs, {value: value[i]})
34 | // Add numeric index suffixes to attributes which should be unique
35 | if (id) {
36 | inputAttrs.id = id + '_' + i
37 | }
38 | if (key) {
39 | inputAttrs.key = id + '_' + i
40 | }
41 | inputs.push(React.createElement('input', inputAttrs))
42 | }
43 | return React.createElement('div', null, inputs)
44 | }
45 |
46 | MultipleHiddenInput.prototype.valueFromData = function(data, files, name) {
47 | if (typeof data[name] != 'undefined') {
48 | return [].concat(data[name])
49 | }
50 | return null
51 | }
52 |
53 | module.exports = MultipleHiddenInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/NullBooleanSelect.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Select = require('./Select')
4 |
5 | /**
6 | * A widget intended to be used with NullBooleanField.
7 | * @constructor
8 | * @extends {Select}
9 | * @param {Object=} kwargs
10 | */
11 | var NullBooleanSelect = Select.extend({
12 | constructor: function NullBooleanSelect(kwargs) {
13 | if (!(this instanceof NullBooleanSelect)) { return new NullBooleanSelect(kwargs) }
14 | kwargs = kwargs || {}
15 | // Set or override choices
16 | kwargs.choices = [['1', 'Unknown'], ['2', 'Yes'], ['3', 'No']]
17 | Select.call(this, kwargs)
18 | }
19 | })
20 |
21 | NullBooleanSelect.prototype.render = function(name, value, kwargs) {
22 | if (value === true || value == '2') {
23 | value = '2'
24 | }
25 | else if (value === false || value == '3') {
26 | value = '3'
27 | }
28 | else {
29 | value = '1'
30 | }
31 | return Select.prototype.render.call(this, name, value, kwargs)
32 | }
33 |
34 | NullBooleanSelect.prototype.valueFromData = function(data, files, name) {
35 | var value = null
36 | if (typeof data[name] != 'undefined') {
37 | var dataValue = data[name]
38 | if (dataValue === true || dataValue == 'True' || dataValue == 'true' ||
39 | dataValue == '2') {
40 | value = true
41 | }
42 | else if (dataValue === false || dataValue == 'False' ||
43 | dataValue == 'false' || dataValue == '3') {
44 | value = false
45 | }
46 | }
47 | return value
48 | }
49 |
50 | module.exports = NullBooleanSelect
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/NumberInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var TextInput = require('./TextInput')
4 |
5 | /**
6 | * An HTML widget.
7 | * @constructor
8 | * @extends {TextInput}
9 | * @param {Object=} kwargs
10 | */
11 | var NumberInput = TextInput.extend({
12 | constructor: function NumberInput(kwargs) {
13 | if (!(this instanceof NumberInput)) { return new NumberInput(kwargs) }
14 | TextInput.call(this, kwargs)
15 | }
16 | , inputType: 'number'
17 | })
18 |
19 | module.exports = NumberInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/PasswordInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var env = require('../env')
6 | var TextInput = require('./TextInput')
7 |
8 | /**
9 | * An HTML widget.
10 | * @constructor
11 | * @extends {TextInput}
12 | * @param {Object=} kwargs
13 | */
14 | var PasswordInput = TextInput.extend({
15 | constructor: function PasswordInput(kwargs) {
16 | if (!(this instanceof PasswordInput)) { return new PasswordInput(kwargs) }
17 | kwargs = object.extend({renderValue: false}, kwargs)
18 | TextInput.call(this, kwargs)
19 | this.renderValue = kwargs.renderValue
20 | }
21 | , inputType: 'password'
22 | })
23 |
24 | PasswordInput.prototype.render = function(name, value, kwargs) {
25 | if (!env.browser && !this.renderValue) {
26 | value = ''
27 | }
28 | return TextInput.prototype.render.call(this, name, value, kwargs)
29 | }
30 |
31 | module.exports = PasswordInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/RadioSelect.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var RadioFieldRenderer = require('./renderers/RadioFieldRenderer')
4 | var RendererMixin = require('./renderers/RendererMixin')
5 | var Select = require('./Select')
6 |
7 | /**
8 | * Renders a single select as a list of elements.
9 | * @constructor
10 | * @extends {Select}
11 | * @param {Object=} kwargs
12 | */
13 | var RadioSelect = Select.extend({
14 | __mixins__: [RendererMixin]
15 | , constructor: function(kwargs) {
16 | if (!(this instanceof RadioSelect)) { return new RadioSelect(kwargs) }
17 | RendererMixin.call(this, kwargs)
18 | Select.call(this, kwargs)
19 | }
20 | , renderer: RadioFieldRenderer
21 | , _emptyValue: ''
22 | })
23 |
24 | module.exports = RadioSelect
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/SelectMultiple.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var is = require('isomorph/is')
4 | var object = require('isomorph/object')
5 | var React = require('react')
6 |
7 | var Select = require('./Select')
8 |
9 | /**
10 | * An HTML widget which allows multiple selections.
11 | * @constructor
12 | * @extends {Select}
13 | * @param {Object=} kwargs
14 | */
15 | var SelectMultiple = Select.extend({
16 | constructor: function SelectMultiple(kwargs) {
17 | if (!(this instanceof SelectMultiple)) { return new SelectMultiple(kwargs) }
18 | Select.call(this, kwargs)
19 | }
20 | , allowMultipleSelected: true
21 | , validation: {onChange: true}
22 | })
23 |
24 | /**
25 | * Renders the widget.
26 | * @param {string} name the field name.
27 | * @param {Array} selectedValues the values of options which should be marked as
28 | * selected, or null if no values are selected - these will be normalised to
29 | * Strings for comparison with choice values.
30 | * @param {Object} [kwargs] additional rendering options.
31 | * @return a element which allows multiple selections.
32 | */
33 | SelectMultiple.prototype.render = function(name, selectedValues, kwargs) {
34 | kwargs = object.extend({choices: []}, kwargs)
35 | if (selectedValues === null) {
36 | selectedValues = []
37 | }
38 | if (!is.Array(selectedValues)) {
39 | selectedValues = [selectedValues]
40 | }
41 | var finalAttrs = this.buildAttrs(kwargs.attrs, {name: name,
42 | multiple: 'multiple'})
43 | var options = this.renderOptions(kwargs.choices)
44 | var valueAttr = (kwargs.controlled ? 'value' : 'defaultValue')
45 | finalAttrs[valueAttr] = selectedValues
46 | return React.createElement('select', finalAttrs, options)
47 | }
48 |
49 | /**
50 | * Retrieves values for this widget from the given data.
51 | * @param {Object} data form data.
52 | * @param {Object} files file data.
53 | * @param {string} name the field name to be used to retrieve data.
54 | * @return {Array} values for this widget, or null if no values were provided.
55 | */
56 | SelectMultiple.prototype.valueFromData = function(data, files, name) {
57 | if (object.hasOwn(data, name) && data[name] != null) {
58 | return [].concat(data[name])
59 | }
60 | return null
61 | }
62 |
63 | module.exports = SelectMultiple
64 |
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/SplitDateTimeWidget.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var DateInput = require('./DateInput')
6 | var MultiWidget = require('./MultiWidget')
7 | var TimeInput = require('./TimeInput')
8 |
9 | /**
10 | * Splits Date input into two elements.
11 | * @constructor
12 | * @extends {MultiWidget}
13 | * @param {Object=} kwargs
14 | */
15 | var SplitDateTimeWidget = MultiWidget.extend({
16 | constructor: function SplitDateTimeWidget(kwargs) {
17 | if (!(this instanceof SplitDateTimeWidget)) { return new SplitDateTimeWidget(kwargs) }
18 | kwargs = object.extend({dateFormat: null, timeFormat: null}, kwargs)
19 | var widgets = [
20 | DateInput({attrs: kwargs.attrs, format: kwargs.dateFormat})
21 | , TimeInput({attrs: kwargs.attrs, format: kwargs.timeFormat})
22 | ]
23 | MultiWidget.call(this, widgets, kwargs.attrs)
24 | }
25 | })
26 |
27 | SplitDateTimeWidget.prototype.decompress = function(value) {
28 | if (value) {
29 | return [
30 | new Date(value.getFullYear(), value.getMonth(), value.getDate())
31 | , new Date(1900, 0, 1, value.getHours(), value.getMinutes(), value.getSeconds())
32 | ]
33 | }
34 | return [null, null]
35 | }
36 |
37 | module.exports = SplitDateTimeWidget
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/SplitHiddenDateTimeWidget.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var SplitDateTimeWidget = require('./SplitDateTimeWidget')
4 |
5 | /**
6 | * Splits Date input into two elements.
7 | * @constructor
8 | * @extends {SplitDateTimeWidget}
9 | * @param {Object=} kwargs
10 | */
11 | var SplitHiddenDateTimeWidget = SplitDateTimeWidget.extend({
12 | constructor: function SplitHiddenDateTimeWidget(kwargs) {
13 | if (!(this instanceof SplitHiddenDateTimeWidget)) { return new SplitHiddenDateTimeWidget(kwargs) }
14 | SplitDateTimeWidget.call(this, kwargs)
15 | for (var i = 0, l = this.widgets.length; i < l; i++) {
16 | this.widgets[i].inputType = 'hidden'
17 | this.widgets[i].isHidden = true
18 | }
19 | }
20 | , isHidden: true
21 | })
22 |
23 | module.exports = SplitHiddenDateTimeWidget
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/SubWidget.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Concur = require('Concur')
4 | var object = require('isomorph/object')
5 |
6 | /**
7 | * Some widgets are made of multiple HTML elements -- namely, RadioSelect.
8 | * This represents the "inner" HTML element of a widget.
9 | * @constructor
10 | */
11 | var SubWidget = Concur.extend({
12 | constructor: function SubWidget(parentWidget, name, value, kwargs) {
13 | if (!(this instanceof SubWidget)) {
14 | return new SubWidget(parentWidget, name, value, kwargs)
15 | }
16 | this.parentWidget = parentWidget
17 | this.name = name
18 | this.value = value
19 | kwargs = object.extend({attrs: null, choices: []}, kwargs)
20 | this.attrs = kwargs.attrs
21 | this.choices = kwargs.choices
22 | }
23 | })
24 |
25 | SubWidget.prototype.render = function() {
26 | var kwargs = {attrs: this.attrs}
27 | if (this.choices.length) {
28 | kwargs.choices = this.choices
29 | }
30 | return this.parentWidget.render(this.name, this.value, kwargs)
31 | }
32 |
33 | module.exports = SubWidget
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/TextInput.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 |
5 | var Input = require('./Input')
6 |
7 | /**
8 | * An HTML widget.
9 | * @constructor
10 | * @extends {Input}
11 | * @param {Object=} kwargs
12 | */
13 | var TextInput = Input.extend({
14 | constructor: function TextInput(kwargs) {
15 | if (!(this instanceof TextInput)) { return new TextInput(kwargs) }
16 | kwargs = object.extend({attrs: null}, kwargs)
17 | if (kwargs.attrs != null) {
18 | this.inputType = object.pop(kwargs.attrs, 'type', this.inputType)
19 | }
20 | Input.call(this, kwargs)
21 | }
22 | , inputType: 'text'
23 | })
24 |
25 | module.exports = TextInput
--------------------------------------------------------------------------------
/src/js/components/common/newforms/widgets/Textarea.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var object = require('isomorph/object')
4 | var React = require('react')
5 |
6 | var Widget = require('../Widget')
7 |
8 | /**
9 | * An HTML