├── .npmignore
├── .gitignore
├── content
├── form1.png
└── reforms.png
├── .github
├── dependabot.yml
├── workflows
│ ├── build.yml
│ └── npm-publish.yml
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── src
├── validator
│ ├── BoolValidator.js
│ ├── RequiredValidator.js
│ └── StringValidator.js
├── output
│ ├── ReformsHiddenOutput.vue
│ ├── ReformsHtmlOutput.vue
│ ├── ReformsPasswordOutput.vue
│ ├── ReformsStringOutput.vue
│ ├── ReformsUrlOutput.vue
│ ├── ReformsTelOutput.vue
│ ├── ReformsEmailOutput.vue
│ ├── ReformsRadioOutput.vue
│ ├── ReformsCheckboxOutput.vue
│ ├── ReformsBooleanOutput.vue
│ ├── ReformsSelectOutput.vue
│ ├── ReformsGroupOutput.vue
│ └── ReformsFileOutput.vue
├── ReformsInputMixin.js
├── ReformsValidators.js
├── ReformsCard.vue
├── input
│ ├── ReformsHiddenInput.vue
│ ├── ReformsBooleanInput.vue
│ ├── ReformsTextInput.vue
│ ├── ReformsTelInput.vue
│ ├── ReformsUrlInput.vue
│ ├── ReformsEmailInput.vue
│ ├── ReformsGroupInput.vue
│ ├── ReformsRadioInput.vue
│ ├── ReformsNumberInput.vue
│ ├── ReformsCheckboxInput.vue
│ ├── ReformsSelectInput.vue
│ ├── ReformsStringInput.vue
│ ├── ReformsHtmlInput.vue
│ ├── ReformsDateInput.vue
│ ├── ReformsPasswordInput.vue
│ └── ReformsFileInput.vue
├── ReformsLabel.vue
├── ReformsFileMixin.js
├── lang
│ ├── en.js
│ └── ru.js
├── ReformsSchema.js
├── Reforms.js
├── ReformsForm.vue
├── ReformsOutput.vue
├── ReformsContainerMixin.js
├── ReformsTypes.js
├── Util.js
└── ReformsInput.vue
├── examples
├── index.js
├── InputsOutputs.vue
├── types
│ ├── NumberType.vue
│ └── StringType.vue
└── ExamplesIndex.vue
├── rollup.config.js
├── LICENSE
├── package.json
├── README.md
├── CODE_OF_CONDUCT.md
└── CONTRIBUTING.md
/.npmignore:
--------------------------------------------------------------------------------
1 | .github/
2 | content/
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | dist/
4 |
--------------------------------------------------------------------------------
/content/form1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malikzh/reforms/HEAD/content/form1.png
--------------------------------------------------------------------------------
/content/reforms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malikzh/reforms/HEAD/content/reforms.png
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/src/validator/BoolValidator.js:
--------------------------------------------------------------------------------
1 | import {createValidator, createMatcher} from "../Util";
2 |
3 | export default {
4 | checked: createValidator(createMatcher(
5 | (value) => value === true,
6 | 'not_checked',
7 | ), 'checked', false),
8 | };
9 |
--------------------------------------------------------------------------------
/src/output/ReformsHiddenOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
--------------------------------------------------------------------------------
/src/ReformsInputMixin.js:
--------------------------------------------------------------------------------
1 | export default {
2 | props: {
3 | id: {
4 | type: String,
5 | default: undefined,
6 | },
7 | isValid: {
8 | default: null,
9 | },
10 | inputClass: [String, Object, Array],
11 | name: String,
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/src/ReformsValidators.js:
--------------------------------------------------------------------------------
1 | import RequiredValidator from './validator/RequiredValidator'
2 | import StringValidator from "./validator/StringValidator";
3 | import BoolValidator from "./validator/BoolValidator";
4 |
5 | export default {
6 | ...RequiredValidator,
7 | ...StringValidator,
8 | ...BoolValidator,
9 | };
10 |
--------------------------------------------------------------------------------
/src/output/ReformsHtmlOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
--------------------------------------------------------------------------------
/src/output/ReformsPasswordOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | ******
4 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/output/ReformsStringOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ value }}
4 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | import {createApp} from 'vue';
2 | import ExamplesIndex from './ExamplesIndex'
3 | import Reforms from "../src/Reforms";
4 |
5 | // Initialize bootstrap 5
6 | import 'bootstrap/dist/js/bootstrap.bundle';
7 | import 'bootstrap/dist/css/bootstrap.css';
8 |
9 | const app = createApp(ExamplesIndex);
10 | app.use(Reforms);
11 | app.mount('#app');
12 |
--------------------------------------------------------------------------------
/examples/InputsOutputs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/src/output/ReformsUrlOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/src/output/ReformsTelOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/output/ReformsEmailOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/output/ReformsRadioOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ options[value] !== undefined ? options[value] : value }}
4 |
5 |
6 |
21 |
--------------------------------------------------------------------------------
/examples/types/NumberType.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
23 |
--------------------------------------------------------------------------------
/src/output/ReformsCheckboxOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ options[v] !== undefined ? options[v] : v }},
5 |
6 |
7 |
8 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [12.x, 14.x, 15.x]
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v1
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | - run: npm ci
25 | - run: npm run build
26 |
--------------------------------------------------------------------------------
/src/ReformsCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
27 |
--------------------------------------------------------------------------------
/src/input/ReformsHiddenInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
26 |
--------------------------------------------------------------------------------
/src/ReformsLabel.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/src/output/ReformsBooleanOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ trueLabel }}
5 |
6 |
7 | {{ falseLabel }}
8 |
9 |
10 | {{ undefinedLabel }}
11 |
12 |
13 |
14 |
37 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import vue from 'rollup-plugin-vue';
2 | import PostCSS from 'rollup-plugin-postcss'
3 | import NodeResolve from '@rollup/plugin-node-resolve'
4 | import commonjs from "rollup-plugin-commonjs";
5 |
6 | export default {
7 | input: 'src/Reforms.js',
8 | output: [
9 | {
10 | file: 'dist/reforms.esm.js',
11 | format: 'es',
12 | },
13 | {
14 | name: 'reforms',
15 | file: 'dist/reforms.umd.js',
16 | format: 'umd',
17 | },
18 | {
19 | file: 'dist/reforms.min.js',
20 | format: 'iife',
21 | },
22 | ],
23 | plugins: [
24 | NodeResolve(),
25 | commonjs(),
26 | vue({
27 | cssModulesOptions: {
28 | generateScopedName: '[local]___[hash:base64:5]',
29 | },
30 | }),
31 | PostCSS()
32 | ],
33 | external: ['vue'],
34 | };
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v1
16 | with:
17 | node-version: 14
18 | - run: npm ci
19 | - run: npm run build
20 |
21 | publish-npm:
22 | needs: build
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v2
26 | - uses: actions/setup-node@v1
27 | with:
28 | node-version: 14
29 | registry-url: https://registry.npmjs.org/
30 | - run: npm ci
31 | - run: npm run build
32 | - run: npm publish
33 | env:
34 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
35 |
--------------------------------------------------------------------------------
/src/input/ReformsBooleanInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 EMPLA GROUP, LLP
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/output/ReformsSelectOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ optionsValues[value] !== undefined ? optionsValues[value] : value }}
5 |
6 |
7 |
8 | {{ optionsValues[v] !== undefined ? optionsValues[v] : v }},
9 |
10 |
11 |
12 |
13 |
47 |
--------------------------------------------------------------------------------
/src/output/ReformsGroupOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
48 |
--------------------------------------------------------------------------------
/src/input/ReformsTextInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
48 |
--------------------------------------------------------------------------------
/src/input/ReformsTelInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
47 |
--------------------------------------------------------------------------------
/src/input/ReformsUrlInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
47 |
--------------------------------------------------------------------------------
/src/input/ReformsEmailInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
47 |
--------------------------------------------------------------------------------
/src/ReformsFileMixin.js:
--------------------------------------------------------------------------------
1 | export default {
2 | props: {
3 | fileSizeUnit: {
4 | type: String,
5 | default: 'iec',
6 | },
7 | },
8 | methods: {
9 | isImage(name) {
10 | return name.match(/\.(?:png|jpe?g|gif|svg|webp)$/);
11 | },
12 | roundSize(x) {
13 | return Math.round(x * 100) / 100;
14 | },
15 | fileSize(size) {
16 | const unitSize = (this.fileSizeUnit === 'si' ? 1000 : 1024);
17 |
18 | const kib = this.roundSize(size / unitSize);
19 |
20 | if (kib < unitSize) {
21 | return kib + (this.fileSizeUnit === 'si' ? ' KB' : ' KiB');
22 | }
23 |
24 | const mib = this.roundSize(kib / unitSize);
25 |
26 | if (mib < unitSize) {
27 | return mib + (this.fileSizeUnit === 'si' ? ' MB' : ' MiB');
28 | }
29 |
30 | const gib = this.roundSize(mib / unitSize);
31 |
32 | if (gib < unitSize) {
33 | return gib + (this.fileSizeUnit === 'si' ? ' GB' : ' GiB');
34 | }
35 |
36 | const tib = this.roundSize(gib / unitSize);
37 | return tib + (this.fileSizeUnit === 'si' ? ' TB' : ' TiB');
38 | },
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/src/input/ReformsGroupInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
50 |
--------------------------------------------------------------------------------
/src/input/ReformsRadioInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
46 |
--------------------------------------------------------------------------------
/examples/types/StringType.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | STRING
6 |
7 |
8 | (single)
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | (multiple)
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
44 |
--------------------------------------------------------------------------------
/src/input/ReformsNumberInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
53 |
--------------------------------------------------------------------------------
/src/input/ReformsCheckboxInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
47 |
--------------------------------------------------------------------------------
/src/lang/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | validation: {
3 | required: {
4 | not_specified: 'This field must be filled',
5 | specified: 'Field is filled',
6 | },
7 | string: {
8 | must_be_string: 'The value must be a string',
9 | },
10 | email: {
11 | must_be_email: 'Incorrect E-mail specified'
12 | },
13 | url: {
14 | must_be_url: 'Invalid URL specified',
15 | },
16 | alpha: {
17 | not_alpha: 'The specified value must contain only Latin letters',
18 | not_alphanum: 'The specified value must contain only Latin letters and numbers'
19 | },
20 | 'in': {
21 | must_be_in: 'The specified value can only be: :values',
22 | must_not_be_in: 'The specified value should not be: :values',
23 | },
24 | regex: {
25 | not_match: 'The specified value does not match the pattern',
26 | },
27 | starts_with: {
28 | not_starts: 'The specified value must start with: ":value"'
29 | },
30 | ends_with: {
31 | not_ends: 'The specified value must end with: ":value"'
32 | },
33 | contains: {
34 | not_contains: 'The specified value does not contain: ":value"',
35 | },
36 | confirmation: {
37 | not_confirmed: 'The fields don\'t match',
38 | },
39 | checked: {
40 | not_checked: 'Need to confirm',
41 | },
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/src/input/ReformsSelectInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ opt2.label }}
8 |
9 |
10 |
11 | {{ opt.label }}
12 |
13 |
14 |
15 |
16 |
17 |
49 |
--------------------------------------------------------------------------------
/src/lang/ru.js:
--------------------------------------------------------------------------------
1 | export default {
2 | validation: {
3 | required: {
4 | not_specified: 'Это поле должно быть заполнено',
5 | specified: 'Поле заполнено',
6 | },
7 | string: {
8 | must_be_string: 'Значение должно быть строкой',
9 | },
10 | email: {
11 | must_be_email: 'Указан некорректный E-mail'
12 | },
13 | url: {
14 | must_be_url: 'Указан некорректный URL',
15 | },
16 | alpha: {
17 | not_alpha: 'Указанное значение должно содержать только латинские буквы',
18 | not_alphanum: 'Указанное значение должно содержать только латинские буквы и цифры'
19 | },
20 | 'in': {
21 | must_be_in: 'Указанное значение может быть только: :values',
22 | must_not_be_in: 'Указанное значение не должно быть: :values',
23 | },
24 | regex: {
25 | not_match: 'Указанное значение не совпадает с шаблоном',
26 | },
27 | starts_with: {
28 | not_starts: 'Указанное значение должно начинаться с ":value"'
29 | },
30 | ends_with: {
31 | not_ends: 'Указанное значение должно заканчиваться строкой ":value"'
32 | },
33 | contains: {
34 | not_contains: 'Указанное значение не содержит строку: ":value"',
35 | },
36 | confirmation: {
37 | not_confirmed: 'Поля не совпадают',
38 | },
39 | checked: {
40 | not_checked: 'Необходимо подтвердить',
41 | },
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/src/input/ReformsStringInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@empla/reforms",
3 | "version": "0.2.3",
4 | "description": "Vue 3 and Bootstrap 5 forms generator",
5 | "main": "dist/reforms.umd.js",
6 | "module": "dist/reforms.esm.js",
7 | "unpkg": "dist/reforms.min.js",
8 | "scripts": {
9 | "test": "echo \"Error: no test specified\" && exit 1",
10 | "dev": "vue-cli-service serve --port=9000 examples/index.js",
11 | "build": "rollup -c"
12 | },
13 | "browser": {
14 | "./sfc": "src/Reforms.js"
15 | },
16 | "publishConfig": {
17 | "access": "public"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/empla/reforms.git"
22 | },
23 | "keywords": [
24 | "vue",
25 | "vue3",
26 | "forms",
27 | "generator",
28 | "ui",
29 | "bootstrap5"
30 | ],
31 | "author": "EMPLA GROUP, LLP",
32 | "license": "MIT",
33 | "bugs": {
34 | "url": "https://github.com/empla/reforms/issues"
35 | },
36 | "homepage": "https://github.com/empla/reforms#readme",
37 | "devDependencies": {
38 | "@babel/core": "^7.13.8",
39 | "@babel/preset-env": "^7.13.9",
40 | "@rollup/plugin-node-resolve": "^11.2.0",
41 | "@vue/cli-service": "^5.0.0-alpha.4",
42 | "@vue/compiler-sfc": "^3.0.5",
43 | "bootstrap": "^5.0.0-beta2",
44 | "cross-env": "^7.0.3",
45 | "rollup": "^2.40.0",
46 | "rollup-plugin-commonjs": "^10.1.0",
47 | "rollup-plugin-postcss": "^4.0.0",
48 | "rollup-plugin-vue": "^6.0.0-beta.10",
49 | "vue": "^3.0.5"
50 | },
51 | "dependencies": {
52 | "flatpickr": "^4.6.9",
53 | "flex-js": "^1.0.5",
54 | "inputmask": "^5.0.5",
55 | "jodit": "^3.6.1",
56 | "lodash": "^4.17.20",
57 | "mitt": "^2.1.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/validator/RequiredValidator.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import {createValidator} from "../Util";
3 |
4 | function isEmpty(value) {
5 | return value === '' || value === null || value === undefined
6 | || (_.isArray(value) && value.length < 1);
7 | }
8 |
9 | export default {
10 | required: createValidator((value, params, lang) => {
11 | const empty = isEmpty(value);
12 |
13 | return {
14 | isValid: !empty,
15 | messages: [
16 | empty ? lang.not_specified : lang.specified,
17 | ],
18 | };
19 | }, 'required', false),
20 | required_with: createValidator((value, params, lang, withoutMode) => {
21 | if (!params.form) {
22 | return;
23 | }
24 |
25 | if (!_.isArray(params.options) || params.options.length < 1) {
26 | throw new Error('Validator "required_if" requires option');
27 | }
28 |
29 | const form = params.form.container;
30 |
31 |
32 | for (const requiredWithFieldName of params.options) {
33 | if ((isEmpty(form[requiredWithFieldName]) && !withoutMode) || (!isEmpty(form[requiredWithFieldName]) && withoutMode)) {
34 | return {
35 | isValid: true,
36 | messages: [
37 | lang.specified
38 | ],
39 | };
40 | }
41 | }
42 |
43 | const empty = isEmpty(value);
44 | const isValid = Boolean(!empty);
45 | return {
46 | isValid: isValid,
47 | messages: [
48 | isValid ? lang.specified : lang.not_specified,
49 | ],
50 | };
51 |
52 | }, 'required', false),
53 |
54 | required_without(params) {
55 | return this.required_with(params, true);
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/src/input/ReformsHtmlInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
67 |
72 |
--------------------------------------------------------------------------------
/src/ReformsSchema.js:
--------------------------------------------------------------------------------
1 | import {h, toRefs, reactive} from 'vue';
2 | import _ from 'lodash';
3 | import ReformsInput from "./ReformsInput.vue";
4 | import ReformsOutput from "./ReformsOutput.vue";
5 |
6 | function renderTree(schema, component, output, prevName) {
7 | let ret = [];
8 |
9 | for (const key of Object.keys(schema)) {
10 | if (!_.isString(schema[key].type)) {
11 | console.warn('Invalid parameter `type` in schema field: ' + key);
12 | continue;
13 | }
14 |
15 | const attrs = _.isObject(schema[key].attrs) ? schema[key].attrs : {};
16 |
17 | let props = reactive({
18 | ...toRefs(component.$attrs),
19 | ...attrs,
20 | type: schema[key].type,
21 | name: !prevName ? key : (prevName + '[' + key + ']'),
22 | value: component.value && component.value[key] ? component.value[key] : null,
23 | });
24 |
25 | if (!output) {
26 | if (!(!_.isFunction(schema[key].showIf)
27 | || (_.isFunction(schema[key].showIf)
28 | && schema[key].showIf(component.$parent.container)))) {
29 | props.shown = false;
30 | }
31 | }
32 |
33 | ret.push(h(output ? ReformsOutput : ReformsInput, props, _.isObject(schema[key].children)
34 | ? (() => renderTree(schema[key].children, component, output, props.name))
35 | : undefined));
36 | }
37 |
38 | return ret;
39 | }
40 |
41 |
42 | export default {
43 | name: 'ReformsSchema',
44 | props: {
45 | schema: {
46 | type: Object,
47 | default: null,
48 | },
49 | outputMode: Boolean,
50 | value: {
51 | type: Object,
52 | default: {},
53 | }
54 | },
55 | render() {
56 | if (!this.schema) {
57 | return;
58 | }
59 |
60 | return renderTree(this.schema, this, this.outputMode);
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/src/input/ReformsDateInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
71 |
76 |
--------------------------------------------------------------------------------
/src/output/ReformsFileOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
63 |
--------------------------------------------------------------------------------
/src/input/ReformsPasswordInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
62 |
71 |
--------------------------------------------------------------------------------
/src/Reforms.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import ReformsInput from "./ReformsInput.vue";
3 | import ReformsOutput from "./ReformsOutput.vue";
4 | import ReformsForm from "./ReformsForm.vue";
5 | import ReformsCard from "./ReformsCard.vue";
6 | import ReformsTypes from "./ReformsTypes";
7 | import ReformsValidators from './ReformsValidators';
8 |
9 | // Plugin main initializer
10 | export default {
11 | /**
12 | * Initialize reforms vue plugin
13 | *
14 | * @param {Object} app
15 | * @param {Object?} options
16 | */
17 | install(app, options) {
18 | let types = {};
19 |
20 | Object.keys(ReformsTypes).forEach((key) => {
21 | types[key] = {
22 | input: ReformsTypes[key].input ? ReformsTypes[key].input.name : null,
23 | output: ReformsTypes[key].output ? ReformsTypes[key].output.name : null,
24 | };
25 | });
26 |
27 | const $reforms = {
28 | types: types,
29 | validators: ReformsValidators,
30 | };
31 |
32 |
33 | if (_.isObject(options) && _.isObject(options.types)) {
34 | options.types.keys().forEach((k) => !void($reforms.types[k] = options.types[k]));
35 | }
36 |
37 | let registeredInputs = [];
38 | let registeredOutputs = [];
39 |
40 | for (const v of Object.values(ReformsTypes)) {
41 | if (v.input && registeredInputs.indexOf(v.input) === -1) {
42 | app.component(v.input.name, v.input);
43 | registeredInputs.push(v.input);
44 | }
45 |
46 | if (v.output && registeredOutputs.indexOf(v.output) === -1) {
47 | app.component(v.output.name, v.output);
48 | registeredOutputs.push(v.output);
49 | }
50 | }
51 |
52 | // Register system properties and components
53 | app.config.globalProperties.$reforms = $reforms;
54 |
55 | app.component(ReformsInput.name, ReformsInput);
56 | app.component(ReformsForm.name, ReformsForm);
57 | app.component(ReformsOutput.name, ReformsOutput);
58 | app.component(ReformsCard.name, ReformsCard);
59 | },
60 | }
61 |
--------------------------------------------------------------------------------
/examples/ExamplesIndex.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
34 |
35 |
36 |
37 |
38 | INPUTS / OUTPUTS
39 |
40 |
41 |
46 |
47 |
48 |
49 |
59 |
60 |
61 |
75 |
--------------------------------------------------------------------------------
/src/ReformsForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
93 |
--------------------------------------------------------------------------------
/src/ReformsOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
104 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Reforms.js
6 |
7 | Vue 3 and Bootstrap 5 forms and cards generator
8 |
9 | ---
10 |
11 | [](https://github.com/empla/reforms/actions/workflows/build.yml)
12 | 
13 | 
14 | 
15 | 
16 |
17 | ---
18 |
19 |
20 |
21 | ## Features
22 |
23 | - Info cards
24 | - Forms
25 | - 15+ inputs types and output types
26 | - Forms schemas
27 | - Plugin system
28 | - Internationalization
29 | - Input & Output groups
30 | - Form validation
31 | - Server side validation
32 | - Multiple and sortable fields
33 |
34 | # Demo
35 |
36 | You can [try it online](https://codesandbox.io/s/reforms-demo-jbpyv)
37 |
38 |
39 | ## Installation
40 |
41 | ```sh
42 | # Install before
43 | npm i vue@next bootstrap@next
44 |
45 | # Install reforms
46 | npm i @empla/reforms
47 | ```
48 |
49 | ## Example
50 |
51 | ### Example with markup
52 |
53 | ```vue
54 |
55 |
56 |
57 |
58 |
59 | Submit
60 |
61 |
62 | ```
63 |
64 | It creates form:
65 |
66 | 
67 |
68 | ### Example with schema
69 |
70 | You can create form with schema:
71 |
72 | ```js
73 | const schema = {
74 | firstname: {
75 | type: 'string',
76 | attrs: {
77 | validation: 'required',
78 | label: 'First name'
79 | }
80 | },
81 | lastname: {
82 | type: 'string',
83 | attrs: {
84 | validation: 'required',
85 | label: 'Last name'
86 | }
87 | },
88 | notes: {
89 | type: 'html',
90 | attrs: {
91 | label: 'Notes',
92 | }
93 | }
94 | };
95 | ```
96 |
97 | And pass it to form:
98 |
99 | ```vue
100 |
101 |
102 | Submit
103 |
104 |
105 | ```
106 |
107 | ## Documentation
108 |
109 | https://reforms.js.org/documentation/
110 |
111 | ## License
112 |
113 | [MIT](LICENSE)
114 |
115 | ---
116 |
117 | Copyright © 2021 EMPLA GROUP, LLP
118 |
119 | Made with ❤️
120 | by [Malik Zharykov](https://github.com/malikzh)️
121 |
--------------------------------------------------------------------------------
/src/ReformsContainerMixin.js:
--------------------------------------------------------------------------------
1 | import {toRef, watch} from 'vue';
2 | import _ from 'lodash';
3 |
4 | export default {
5 | data() {
6 | return {
7 | containerInputs: {},
8 | container: {},
9 | containerWatchers: {},
10 | containerListeners: {},
11 | };
12 | },
13 | methods: {
14 | registerInput(input) {
15 | if (!this.container) {
16 | this.container = {};
17 | }
18 |
19 | this.containerListeners[input.name] = {
20 | modelValue: (modelValue) => {
21 | if (!input.$props.shown) {
22 | this.container[input.name] = undefined;
23 | return
24 | }
25 |
26 | this.container[input.name] = modelValue;
27 | },
28 | shown: (shown) => {
29 | if (!shown) {
30 | this.container[input.name] = undefined;
31 | } else {
32 | input.updateValues();
33 | }
34 | },
35 | }
36 |
37 | input.events.on('out:modelValue', this.containerListeners[input.name].modelValue);
38 | input.events.on('out:shown', this.containerListeners[input.name].shown);
39 |
40 | this.containerWatchers[input.name] = {
41 | containerWatch: watch(toRef(this.container, input.name), (value) => {
42 | if (!input.$props.shown) {
43 | return;
44 | }
45 |
46 | input.events.emit('in:modelValue', value);
47 | }, {deep: true, immediate: true}),
48 | validationWatch: watch(toRef(this.$props, 'validationResult'), (validationResult) => {
49 | if (_.isArray(validationResult[input.name])) {
50 | input.inputValidation = validationResult[input.name];
51 | }
52 | }, {deep: true, immediate: true}),
53 | validatorsWatch: watch(toRef(this.$props, 'validation'), (validation) => {
54 | if (_.isArray(validation)) {
55 | input.validators = validation;
56 | }
57 | }, {deep: true, immediate: true}),
58 | };
59 |
60 | this.containerInputs[input.name] = input;
61 | },
62 | unregisterInput(name) {
63 | if (!this.container[name]) {
64 | return;
65 | }
66 |
67 | this.container[name] = undefined;
68 | this.containerWatchers[name].containerWatch();
69 | this.containerWatchers[name].validationWatch();
70 | this.containerWatchers[name].validatorsWatch();
71 | this.containerWatchers[name] = undefined;
72 | this.containerInputs[name].events.off('out:modelValue', this.containerListeners[name].modelValue);
73 | this.containerInputs[name].events.off('out:shown', this.containerListeners[name].shown);
74 | this.containerInputs[name] = undefined;
75 | this.containerListeners[name] = undefined;
76 | }
77 | },
78 | };
79 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at contact@empla.group. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/src/ReformsTypes.js:
--------------------------------------------------------------------------------
1 | import ReformsStringInput from "./input/ReformsStringInput.vue";
2 | import ReformsStringOutput from "./output/ReformsStringOutput.vue";
3 | import ReformsGroupInput from "./input/ReformsGroupInput.vue";
4 | import ReformsGroupOutput from './output/ReformsGroupOutput.vue';
5 | import ReformsNumberInput from "./input/ReformsNumberInput.vue";
6 | import ReformsTextInput from "./input/ReformsTextInput.vue";
7 | import ReformsEmailInput from "./input/ReformsEmailInput.vue";
8 | import ReformsEmailOutput from "./output/ReformsEmailOutput.vue";
9 | import ReformsTelInput from "./input/ReformsTelInput.vue";
10 | import ReformsTelOutput from "./output/ReformsTelOutput.vue";
11 | import ReformsUrlInput from "./input/ReformsUrlInput.vue";
12 | import ReformsUrlOutput from "./output/ReformsUrlOutput.vue";
13 | import ReformsHiddenInput from "./input/ReformsHiddenInput.vue";
14 | import ReformsHiddenOutput from "./output/ReformsHiddenOutput.vue";
15 | import ReformsRadioInput from "./input/ReformsRadioInput.vue";
16 | import ReformsRadioOutput from "./output/ReformsRadioOutput.vue";
17 | import ReformsCheckboxInput from "./input/ReformsCheckboxInput.vue";
18 | import ReformsCheckboxOutput from "./output/ReformsCheckboxOutput.vue";
19 | import ReformsBooleanInput from "./input/ReformsBooleanInput.vue";
20 | import ReformsBooleanOutput from "./output/ReformsBooleanOutput.vue";
21 | import ReformsSelectInput from "./input/ReformsSelectInput.vue";
22 | import ReformsSelectOutput from "./output/ReformsSelectOutput.vue";
23 | import ReformsDateInput from "./input/ReformsDateInput.vue";
24 | import ReformsHtmlInput from "./input/ReformsHtmlInput.vue";
25 | import ReformsHtmlOutput from "./output/ReformsHtmlOutput.vue";
26 | import ReformsFileInput from "./input/ReformsFileInput.vue";
27 | import ReformsFileOutput from "./output/ReformsFileOutput.vue";
28 | import ReformsPasswordInput from "./input/ReformsPasswordInput.vue";
29 | import ReformsPasswordOutput from "./output/ReformsPasswordOutput.vue";
30 |
31 | //
32 | // Reforms default components
33 | //
34 | export default {
35 | 'group': {
36 | input: ReformsGroupInput,
37 | output: ReformsGroupOutput,
38 | },
39 | 'string': {
40 | input: ReformsStringInput,
41 | output: ReformsStringOutput,
42 | },
43 | 'number': {
44 | input: ReformsNumberInput,
45 | output: ReformsStringOutput,
46 | },
47 | 'text': {
48 | input: ReformsTextInput,
49 | output: ReformsStringOutput,
50 | },
51 | 'email': {
52 | input: ReformsEmailInput,
53 | output: ReformsEmailOutput,
54 | },
55 | 'tel': {
56 | input: ReformsTelInput,
57 | output: ReformsTelOutput,
58 | },
59 | 'url': {
60 | input: ReformsUrlInput,
61 | output: ReformsUrlOutput,
62 | },
63 | 'hidden': {
64 | input: ReformsHiddenInput,
65 | output: ReformsHiddenOutput,
66 | },
67 | 'radio': {
68 | input: ReformsRadioInput,
69 | output: ReformsRadioOutput,
70 | },
71 | 'checkbox': {
72 | input: ReformsCheckboxInput,
73 | output: ReformsCheckboxOutput,
74 | },
75 | 'bool': {
76 | input: ReformsBooleanInput,
77 | output: ReformsBooleanOutput,
78 | },
79 | 'select': {
80 | input: ReformsSelectInput,
81 | output: ReformsSelectOutput,
82 | },
83 | 'datetime': {
84 | input: ReformsDateInput,
85 | output: ReformsStringOutput,
86 | },
87 | 'html': {
88 | input: ReformsHtmlInput,
89 | output: ReformsHtmlOutput,
90 | },
91 | 'file': {
92 | input: ReformsFileInput,
93 | output: ReformsFileOutput,
94 | },
95 | 'password': {
96 | input: ReformsPasswordInput,
97 | output: ReformsPasswordOutput,
98 | },
99 | };
100 |
--------------------------------------------------------------------------------
/src/validator/StringValidator.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import {createValidator, createMatcher} from "../Util";
3 |
4 | export default {
5 | string: createValidator(createMatcher((v) => _.isString(v),
6 | 'must_be_string'), 'string', true),
7 | email: createValidator(createMatcher(
8 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/gi,
9 | 'must_be_email'), 'email', true),
10 | url: createValidator(createMatcher(
11 | /^(https?):\/\/(-\.)?([^\s\/?\.#-]+\.?)+(\/[^\s]*)?$/gi,
12 | 'must_be_url'), 'url', true),
13 | alpha: createValidator(createMatcher(
14 | /^[a-z]+$/gi,
15 | 'not_alpha',
16 | ), 'alpha', true),
17 | alphanum: createValidator(createMatcher(/^[a-z0-9]+$/gi,'not_alphanum'), 'alpha', true),
18 | 'in': createValidator(createMatcher(
19 | (value, params, lang, langParams) => {
20 | langParams['values'] = params.options.join(', ');
21 | return !_.isArray(params.options) || params.options.includes(value);
22 | },
23 | 'must_be_in'), 'in', true),
24 | not_in: createValidator(createMatcher(
25 | (value, params, lang, langParams) => {
26 | langParams['values'] = params.options.join(', ');
27 | return !_.isArray(params.options) || !params.options.includes(value);
28 | },
29 | 'must_not_be_in'), 'in', true),
30 | regex: createValidator(createMatcher((value, params) =>
31 | !_.isArray(params.options) || params.options.length < 1 || Boolean(String(value).match(new RegExp(params.options[0], params.options.length > 1 ? params.options[1] : undefined))), 'not_match'), 'regex', true),
32 | starts_with: createValidator(createMatcher((value, params, lang, langParams) => {
33 | langParams['value'] = _.isArray(params.options) && params.options.length > 0 ? params.options[0] : '';
34 |
35 | return !_.isArray(params.options) || params.options.length < 1 || String(value).startsWith(params.options[0]);
36 | },
37 | 'not_starts'
38 | ), 'starts_with', true),
39 | ends_with: createValidator(createMatcher((value, params, lang, langParams) => {
40 | langParams['value'] = _.isArray(params.options) && params.options.length > 0 ? params.options[0] : '';
41 |
42 | return !_.isArray(params.options) || params.options.length < 1 || String(value).endsWith(params.options[0]);
43 | },
44 | 'not_ends'
45 | ), 'ends_with', true),
46 | contains: createValidator(createMatcher((value, params, lang, langParams) => {
47 | langParams['value'] = _.isArray(params.options) && params.options.length > 0 ? params.options[0] : '';
48 |
49 | return !_.isArray(params.options) || params.options.length < 1 || String(value).includes(params.options[0]);
50 | },
51 | 'not_contains'
52 | ), 'contains', true),
53 | confirmation: createValidator(createMatcher((value, params, lang) => {
54 | if (!_.isArray(params.options) || params.options.length < 1) {
55 | return true;
56 | }
57 |
58 |
59 | const values = (params.form && params.form.container) || {};
60 | const valid = values[params.options[0]] === value;
61 |
62 |
63 | (params.form && params.form.containerInputs && params.form.containerInputs[params.options[0]] && (() => {
64 | const input = params.form.containerInputs[params.options[0]];
65 |
66 | if (valid) {
67 | input.inputValidation = [
68 | {
69 | isValid: true,
70 | messages: [],
71 | }
72 | ];
73 | } else {
74 | input.inputValidation = [
75 | {
76 | isValid: false,
77 | messages: [
78 | lang['not_confirmed'],
79 | ],
80 | }
81 | ];
82 | }
83 | })());
84 |
85 | return valid;
86 | }, 'not_confirmed'), 'confirmation', true),
87 | };
88 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
11 | build.
12 | 2. Update the README.md with details of changes to the interface, this includes new environment
13 | variables, exposed ports, useful file locations and container parameters.
14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
17 | do not have permission to do that, you may request the second reviewer to merge it for you.
18 |
19 | ## Code of Conduct
20 |
21 | ### Our Pledge
22 |
23 | In the interest of fostering an open and welcoming environment, we as
24 | contributors and maintainers pledge to making participation in our project and
25 | our community a harassment-free experience for everyone, regardless of age, body
26 | size, disability, ethnicity, gender identity and expression, level of experience,
27 | nationality, personal appearance, race, religion, or sexual identity and
28 | orientation.
29 |
30 | ### Our Standards
31 |
32 | Examples of behavior that contributes to creating a positive environment
33 | include:
34 |
35 | * Using welcoming and inclusive language
36 | * Being respectful of differing viewpoints and experiences
37 | * Gracefully accepting constructive criticism
38 | * Focusing on what is best for the community
39 | * Showing empathy towards other community members
40 |
41 | Examples of unacceptable behavior by participants include:
42 |
43 | * The use of sexualized language or imagery and unwelcome sexual attention or
44 | advances
45 | * Trolling, insulting/derogatory comments, and personal or political attacks
46 | * Public or private harassment
47 | * Publishing others' private information, such as a physical or electronic
48 | address, without explicit permission
49 | * Other conduct which could reasonably be considered inappropriate in a
50 | professional setting
51 |
52 | ### Our Responsibilities
53 |
54 | Project maintainers are responsible for clarifying the standards of acceptable
55 | behavior and are expected to take appropriate and fair corrective action in
56 | response to any instances of unacceptable behavior.
57 |
58 | Project maintainers have the right and responsibility to remove, edit, or
59 | reject comments, commits, code, wiki edits, issues, and other contributions
60 | that are not aligned to this Code of Conduct, or to ban temporarily or
61 | permanently any contributor for other behaviors that they deem inappropriate,
62 | threatening, offensive, or harmful.
63 |
64 | ### Scope
65 |
66 | This Code of Conduct applies both within project spaces and in public spaces
67 | when an individual is representing the project or its community. Examples of
68 | representing a project or community include using an official project e-mail
69 | address, posting via an official social media account, or acting as an appointed
70 | representative at an online or offline event. Representation of a project may be
71 | further defined and clarified by project maintainers.
72 |
73 | ### Enforcement
74 |
75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
77 | complaints will be reviewed and investigated and will result in a response that
78 | is deemed necessary and appropriate to the circumstances. The project team is
79 | obligated to maintain confidentiality with regard to the reporter of an incident.
80 | Further details of specific enforcement policies may be posted separately.
81 |
82 | Project maintainers who do not follow or enforce the Code of Conduct in good
83 | faith may face temporary or permanent repercussions as determined by other
84 | members of the project's leadership.
85 |
86 | ### Attribution
87 |
88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
89 | available at [http://contributor-covenant.org/version/1/4][version]
90 |
91 | [homepage]: http://contributor-covenant.org
92 | [version]: http://contributor-covenant.org/version/1/4/
93 |
--------------------------------------------------------------------------------
/src/input/ReformsFileInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
69 |
70 |
208 |
--------------------------------------------------------------------------------
/src/Util.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | const Flex = require('flex-js');
3 |
4 | export const CHARS_ALPHA_LO = 'abcdefghijklmnopqrstuvwxyz';
5 | export const CHARS_ALPHA_HI = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
6 | export const CHARS_NUMBER = '0123456789';
7 |
8 | /**
9 | * Create getter & setter for v-model value
10 | *
11 | * @param {String?} propName v-model Property name
12 | * @returns {{set(*=): void, get(): *}|*}
13 | */
14 | export function modelValue(propName, castTo) {
15 | propName = propName || 'modelValue';
16 | castTo = castTo || ((value) => value);
17 |
18 |
19 | return {
20 | get() {
21 | return castTo(this[propName]);
22 | },
23 | set(value) {
24 | this.$emit('update:' + propName, castTo(value));
25 | },
26 | };
27 | }
28 |
29 | /**
30 | * Merge classes for vue v-bind:class={}
31 | *
32 | * @param {String|Object|String[]}classes
33 | * @returns {Object}
34 | */
35 | export function mergeClasses(...classes) {
36 | let result = {};
37 |
38 | const stringArrayToObject = (stringArray, result) => {
39 | stringArray.forEach((className) => {
40 | result[className] = true;
41 | });
42 | };
43 |
44 | for (const clazz of classes) {
45 | if (_.isString(clazz)) {
46 | stringArrayToObject(clazz.split(' ').filter((k) => Boolean(k)), result);
47 | } else if (_.isArray(clazz)) {
48 | stringArrayToObject(clazz.filter((k) => Boolean(k)), result);
49 | } else if (_.isObject(clazz)) {
50 | _.merge(result, clazz);
51 | }
52 | }
53 |
54 | return result;
55 | }
56 |
57 | /**
58 | * Generate random string from characters
59 | *
60 | * @param {number} length
61 | * @param {string} characters
62 | * @returns {string}
63 | */
64 | export function randomStringFromChars(length, characters) {
65 | let result = '';
66 | for (let i = 0; i < length; i++ ) {
67 | result += characters.charAt(Math.floor(Math.random() * characters.length));
68 | }
69 | return result;
70 | }
71 |
72 | /**
73 | * Return random id
74 | *
75 | * @param {number} length
76 | * @returns {string}
77 | */
78 | export function randomId(length) {
79 | return randomStringFromChars(length > 0 ? 1 : 0, CHARS_ALPHA_LO + CHARS_ALPHA_HI)
80 | + randomStringFromChars(length - 1, CHARS_ALPHA_HI + CHARS_ALPHA_LO + CHARS_NUMBER);
81 | }
82 |
83 | /**
84 | * Create validator function
85 | *
86 | * @param {Function} valueValidator
87 | * @param {String} lang
88 | * @param {Boolean} skipNil
89 | * @return {Function}
90 | */
91 | export function createValidator(valueValidator, lang, skipNil) {
92 | return function(params, ...args) {
93 | let result = [];
94 |
95 | for (const value of (params.multiple ? params.value : [params.value])) {
96 |
97 | if (skipNil && _.isNil(value)) {
98 | result.push({
99 | isValid: true,
100 | messages: [],
101 | });
102 | continue;
103 | }
104 |
105 | const v = valueValidator(value, params, params.lang.validation[lang], ...args);
106 |
107 | if (v === false) {
108 | break;
109 | }
110 |
111 | if (_.isObject(v)) {
112 | result.push(v);
113 | }
114 | }
115 |
116 | return result;
117 | };
118 | }
119 |
120 | export function translate(str, params) {
121 | return Object.entries(params).reduce(
122 | (str, [key, value]) => str.replaceAll(':' + key, value), str);
123 | }
124 |
125 | /**
126 | * Create matcher function for validator
127 | *
128 | * @param {Function|RegExp} matchFunc
129 | * @param {String} langNotOk
130 | * @param {String?} langOk
131 | * @return {function(*=, *=, *=): {isValid: boolean, messages: [*]|[]|[*]}}
132 | */
133 | export function createMatcher(matchFunc, langNotOk, langOk) {
134 | return function (value, params, lang, ...args) {
135 | let langParams = {};
136 |
137 | const isValid = Boolean(_.isRegExp(matchFunc) ? String(value).match(matchFunc) : matchFunc(value, params, lang, langParams, ...args));
138 |
139 | return {
140 | isValid: isValid,
141 | messages: (isValid ? (langOk ? [translate(lang[langOk], langParams)] : []) : [translate(lang[langNotOk], langParams)]),
142 | };
143 | };
144 | }
145 |
146 | /**
147 | * Parse validation rules in string
148 | *
149 | * @param {String} validationRules
150 | * @return {Object}
151 | */
152 | export function parseValidationRules(validationRules) {
153 | let tokens = [];
154 |
155 | const lex = new Flex();
156 | lex.setIgnoreCase(true);
157 | lex.addState('param', true);
158 | lex.addStateRule(Flex.STATE_INITIAL, /[a-z0-9_\-]+/
159 | , (lexer) => {
160 | tokens.push({
161 | id: 'rulename',
162 | value: lexer.text,
163 | });
164 |
165 | const inp = lexer.input();
166 |
167 | if (inp === ':') {
168 | lexer.begin('param');
169 | } else if (inp === '|') {
170 | lexer.begin(Flex.STATE_INITIAL);
171 | } else if (inp === '') {
172 | ;
173 | } else {
174 | throw new Error('Unexpected char: "' + inp + '"');
175 | }
176 | });
177 | lex.addStateRule('param', /[^,"'|]+/
178 | , (lexer) => {
179 | tokens.push({
180 | id: 'param',
181 | value: lexer.text,
182 | });
183 |
184 | const inp = lexer.input();
185 |
186 | if (inp === ',') {
187 | return;
188 | } else if (inp === '|') {
189 | lexer.begin(Flex.STATE_INITIAL);
190 | }
191 | });
192 |
193 | lex.addStateRule('param', /("|')/, (lexer) => {
194 | let char = '';
195 | let str = '';
196 | const quote = lexer.text;
197 |
198 | do {
199 | char = lexer.input();
200 |
201 | if (char === quote) {
202 | break;
203 | }
204 |
205 | if (char === '\\') {
206 | const esc = lexer.input();
207 |
208 | if (esc === '"') {
209 | str += '"';
210 | } else if (esc === '\'') {
211 | str += '\'';
212 | } else if (esc === 't') {
213 | str += '\t';
214 | } else {
215 | str += '\\';
216 | }
217 |
218 | continue;
219 | }
220 |
221 | str += char;
222 |
223 | } while (char !== '');
224 |
225 | tokens.push({
226 | id: "param",
227 | value: str,
228 | });
229 |
230 | if (lexer.input() !== ',') {
231 | lexer.terminate();
232 | }
233 | });
234 |
235 | lex.setSource(validationRules);
236 | lex.lex();
237 |
238 | let params = [];
239 |
240 | let result = [];
241 |
242 | for (const token of tokens.reverse()) {
243 | if (token.id === 'param') {
244 | params.push(token);
245 | } else {
246 | result.push({
247 | name: token.value,
248 | options: params.map((v) => v.value),
249 | });
250 | params = [];
251 | }
252 | }
253 |
254 | return result.reverse();
255 | }
256 |
257 | export function passwordStrengthEstimate(password) {
258 | let est = 0;
259 |
260 | if (password.length >= 8) {
261 | ++est;
262 | }
263 |
264 | if ((/[^a-zA-Z0-9]/).test(password)) {
265 | ++est;
266 | }
267 |
268 | if ((/[a-z]/).test(password) && (/[A-Z]/).test(password)) {
269 | ++est;
270 | }
271 |
272 | if ((/[0-9]/).test(password)) {
273 | ++est;
274 | }
275 |
276 | return est;
277 | };
278 |
--------------------------------------------------------------------------------
/src/ReformsInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
351 |
--------------------------------------------------------------------------------