add plugins

This commit is contained in:
2019-10-13 21:45:30 +02:00
parent 4eac59dd5f
commit f28cc01e82
1860 changed files with 225261 additions and 2 deletions
+165
View File
@@ -0,0 +1,165 @@
{
"env": {
"browser": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"rules": {
"accessor-pairs": 2,
"array-bracket-spacing": 0,
"block-scoped-var": 0,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"camelcase": 0,
"comma-dangle": [2, "never"],
"comma-spacing": [2, { "before": false, "after": true }],
"comma-style": [2, "last"],
"complexity": 0,
"computed-property-spacing": 0,
"consistent-return": 0,
"consistent-this": 0,
"constructor-super": 2,
"curly": [2, "multi-line"],
"default-case": 0,
"dot-location": [2, "property"],
"dot-notation": 0,
"eol-last": 2,
"eqeqeq": [2, "allow-null"],
"func-names": 0,
"func-style": 0,
"generator-star-spacing": [2, { "before": true, "after": true }],
"guard-for-in": 0,
"handle-callback-err": [2, "^(err|error)$" ],
"indent": [2, 4, { "SwitchCase": 1 }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"linebreak-style": 0,
"lines-around-comment": 0,
"max-nested-callbacks": 0,
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
"new-parens": 2,
"newline-after-var": 0,
"no-alert": 0,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 0,
"no-cond-assign": 2,
"no-console": 0,
"no-constant-condition": 0,
"no-continue": 0,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 0,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-else-return": 0,
"no-empty": 0,
"no-empty-character-class": 2,
"no-eq-null": 0,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": 0,
"no-extra-semi": 0,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-inline-comments": 0,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 0,
"no-loop-func": 0,
"no-mixed-requires": 0,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multiple-empty-lines": [2, { "max": 1 }],
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-nested-ternary": 0,
"no-new": 2,
"no-new-func": 0,
"no-new-object": 2,
"no-new-require": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-param-reassign": 0,
"no-path-concat": 0,
"no-process-env": 0,
"no-process-exit": 0,
"no-proto": 0,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-modules": 0,
"no-return-assign": 2,
"no-script-url": 0,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": 0,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undef-init": 2,
"no-undefined": 0,
"no-underscore-dangle": 0,
"no-unexpected-multiline": 2,
"no-unneeded-ternary": 2,
"no-unreachable": 2,
"no-unused-expressions": 0,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-use-before-define": 0,
"no-var": 0,
"no-void": 0,
"no-warning-comments": 0,
"no-with": 2,
"object-curly-spacing": 0,
"object-shorthand": 0,
"one-var": [2, { "initialized": "never" }],
"operator-assignment": 0,
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
"padded-blocks": 0,
"prefer-const": 0,
"quote-props": 0,
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"semi": [2, "always"],
"semi-spacing": 0,
"sort-vars": 0,
"keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
"strict": 0,
"use-isnan": 2,
"valid-jsdoc": 0,
"valid-typeof": 2,
"vars-on-top": 0,
"wrap-iife": [2, "any"],
"wrap-regex": 0,
"yoda": [2, "never"]
}
}
+5
View File
@@ -0,0 +1,5 @@
# OS Generated
.DS_Store*
/.idea
node_modules
*.js.map
+900
View File
@@ -0,0 +1,900 @@
# v3.0.9
## 09/19/2019
1. [](#improved)
* Removed jQuery dependency for the reCaptcha field and VanillaJS-ified it instead
* Updated to ReCaptcha library version `1.2.3`
1. [](#bugfix)
* Fixed `Badly encoded JSON data` warning when uploading files [grav#2663](https://github.com/getgrav/grav/issues/2663)
# v3.0.8
## 08/14/2019
1. [](#improved)
* Change form save action location to `user-data://` stream [#353](https://github.com/getgrav/grav-plugin-form/issues/353)
* Updated `eu`, `fr` and `pl` language
* Make `Form::initialize()` chainable
* Added `folder` option to `save:` action with fallback
1. [](#bugfix)
* Fixed Submit & Redirect not working as expected [#355](https://github.com/getgrav/grav-plugin-form/issues/355)
* Fixed oversensitive refresh prevention [#354](https://github.com/getgrav/grav-plugin-form/issues/354)
* Fixed issue with Form JS when pipeline is enabled [grav#2592](https://github.com/getgrav/grav/issues/2592)
* Fixed `accept` for SVG in file field [#364](https://github.com/getgrav/grav-plugin-form/pull/364)
* Fixed issue with plugin not returning expected form [#309](https://github.com/getgrav/grav-plugin-form/pull/309)
* Fixed form message not showing up after reset process
* Fixed form fields inside a single tab not using value from the form object if it is available
* Fixed file form field failing resolution checks in certain circumstances
# v3.0.7
## 07/01/2019
1. [](#bugfix)
* Fixed file upload when `$grav['user']` is not set [#352](https://github.com/getgrav/grav-plugin-form/issues/352)
* Fixed label markdown being escaped [#356](https://github.com/getgrav/grav-plugin-form/pull/356)
# v3.0.6
## 06/24/2019
1. [](#bugfix)
* Fixed regression breaking forms external to the page in some sites
* Fixed regression with form action in sub-path folders
# v3.0.5
## 06/21/2019
1. [](#new)
* Added support for form state saving with dynamic unique id appended to the URL
1. [](#improved)
* Avoid creating form state if there is no data to be saved
1. [](#bugfix)
* Fixed missing check for maximum allowed files in `files` field
* Fixed unique form ids getting cached, they should change on every page reload
# v3.0.4
## 06/14/2019
1. [](#improved)
* Captcha field: fail silently and display error in console if site_key was not defined
* Support inline-errors, prepend, append in `textarea`
1. [](#bugfix)
* Use less-strict comparison when checking version 3 [#344](https://github.com/getgrav/grav-plugin-form/issues/344)
# v3.0.3
## 05/09/2019
1. [](#new)
* Added Text `field.copy-to-clipboard` which can be used by admin plugin
1. [](#bugfix)
* Fixed Flex route issue in list page
* Fix flex-height of signature field
* Fix for broken `field.recaptcha_site_key` [#344](https://github.com/getgrav/grav-plugin-form/issues/344)
* Fix for checkbox data lang string [#343](https://github.com/getgrav/grav-plugin-form/issues/343)
* Fix for duplicate inline error messages [#337](https://github.com/getgrav/grav-plugin-form/issues/337)
* Fixed bad folder permissions when creating folder for file uploads
# v3.0.2
## 04/22/2019
1. [](#new)
* Support for Google Recaptcha theme (light/dark) doesn't work in v3 yet.
1. [](#improved)
* Visual upgrade for form field descriptions [#335](https://github.com/getgrav/grav-plugin-form/pull/335)
1. [](#bugfix)
* Fixed issue with `recaptcha_not_validated` property not being used
# v3.0.1
## 04/15/2019
1. [](#new)
* Added support for form task in blueprint
1. [](#bugfix)
* Fix url field output in list view
# v3.0.0
## 04/11/2019
1. [](#new)
* Allow streams in `file` field widget [#119](https://github.com/getgrav/grav-plugin-form/issues/119)
* Use new unified `|t` translate filter in all fields
* Google reCAPTCHA v3 support added
* Google reCAPTCHA v2 Invisible support added
* Added mutliple forms with reCAPTCHA support
* Form no longer extends `Grav\Common\Iterator` (may have some backward compatibility issues with plugins, likely not)
* Form now uses `NestedArrayAccessWithGetters` (with '/' separator) and `FormTrait` traits
* Added `view`, `key`, `ignore`, `section`, `toggle`, `tabs` and `tab` form fields
* Added support for `toggleable` inputs, which can be disabled/enabled by user
* Added `$grav['forms']` to allow plugins to better use forms [#307](https://github.com/getgrav/grav-plugin-form/pull/307)
* Added support for custom form types
* Forms can now remember their state after page reload with YAML `datasets: store-state: true` set in the fields
* Added `clear-state` AJAX task
* Added task to clear form flash
* Added support for file-upload and file-remove tasks
* Added ability to set a custom `clear_redirect_url` on a form
* Added `Form::setMessage()` method
* Added new form field templates for edit list table
* Requires Grav 1.6.0-beta.7 (and optionally Admin 1.9.0-beta.7)
* Backwards incompatibility: Do not allow static `Form::getNonce()` call, only `$form->getNonce()` works now
* Backwards incompatibility: All form field twig files are required to extend `field.html.twig` to work properly
* Allow using custom nonce field/action by setting `nonce.name` and `nonce.action` inside the form YAML
* Added `html: true` support for form buttons (will not escape the button value)
* Added `toggle`, `tabs` and `tab` form fields
* Added support for toggleable inputs, which can be disabled/enabled by user
* Added proper support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar` to any field
1. [](#improved)
* Make fields `formname`, `uniqueid` and `honeypot` non-inputs in form validation
* Update all Form classes to rely on `PageInterface` instead of `Page` class
* Removed `media.upload_limit` references
* Added field type `hidden` to `ip` and `timestamp` actions
* Improved the `hidden` field logic to support `value` or `default` set
* Set the message globally on `messages` object when using a redirect in form
* Improved logic for finding the current form
* Added support for data-sets in `textarea` and `select` fields
* Simplify `shouldProcessForm()` logic
* Do not cache flat forms list, regenerate it instead
* Fixed some inconsistencies on how blueprints are handled
* Improved uploads handling, added new `upload: true|false` process
* Make `Form` implement `FormInterface`
* Added `field.size` in `array`, `select`, and `textarea`
* Enable forms in admin plugin
* Removed submit of unchecked fields in frontend
* Make sure that the images in the file field are not cached in browser
* Updated code to use PHP 7.1 features
* Added some extra blocks to `file` field to make it more extensible
* Added `field.classes` to form field to allow customization
* Used Google reCAPTCHA API all token validation
* Better filename and mime type handling
* Now using the new core Grav language prefix
* Make all form fields to extend `field.html.twig`
1. [](#bugfix)
* Fixed old way to access form name
* Fixed minor bugs
* Fixed null date/time in list view
* Fixed forms not being cached properly
* Fixed issue with `selectize`, automatically selecting an unintended value
* Throw exception if you try to `add` to a file and don't provide `filename` [#324](https://github.com/getgrav/grav-plugin-form/issues/324)
* Fixed file field saving with nested name
* Fixed file saving if destination folder does not exist
* Fixed FormFlash object not getting deleted on form post
* Regression: Fixed ignored form action [#318](https://github.com/getgrav/grav-plugin-form/issues/318)
* Regression: Fixed modular form submit not triggering the action sometimes
* Fixed modular form submits without defined `action: /path` inside the form
* Fixed form processing in nested modular pages
* Fixed container fields breaking values from the child fields
* Fixed form fields not accepting object values
* Fixed some form fields having no value for nested field sets
* Fixed double escaping of `file` type input JSON value
* Fixed double locking of file when calling processor save
* Fixed some missing backwards compatibility
* Fixed some issues with flashed form
* Fixed Twig 2 compatibility issue
* Fixed files uploading before captcha check
* Fixed files uploading before data has been stored
* Fixed some issues with reCAPTCHA v3
* Fixed error responses when file actions fail in the form
* Pass unique_id when uploading files if available
# v2.16.4
## 12/14/2018
1. [](#improved)
* Better handling of invalid file names during upload
* Better MIME type checking of files during file upload
* Do not rely on jQuery for merging languages from form fields [#290](https://github.com/getgrav/grav-plugin-form/issues/290) [#291](https://github.com/getgrav/grav-plugin-form/issues/291)
2. [](#bugfix)
* Remove jQuery dependency in form.html.twig (#290)
3. [](#new)
* Added Object.assign-polyfill (#291)
# v2.16.3
## 09/21/2018
1. [](#improved)
* Use `Url:post()` to get the `$_POST` variable (allows common security checks/filtering for the POST data)
* Various JS tweaks and enhancements
1. [](#bugfix)
* Fixed issue where `select` set up as `multiple` and with `selectize: create: true` would not properly merge newly created values on rendering.
# v2.16.2
## 08/23/2018
1. [](#improved)
* Switched to new Grav `Yaml` class to support Native + Fallback YAML libraries
* Simple styling fixes for `array` field
1. [](#bugfix)
* Fixed issue with translations of placeholder text in `array` field
# v2.16.1
## 08/20/2018
1. [](#new)
* Fixed a regression issue with `file` & `array` field
# v2.16.0
## 08/20/2018
1. [](#new)
* Added new `form.keep_alive` option to keep session alive [#275](https://github.com/getgrav/grav-plugin-form/issues/275)
* Added `array` field for frontend use
1. [](#improved)
* Improving compatibility `autocomplete` spec [#274](https://github.com/getgrav/grav-plugin-form/pull/274)
# v2.15.1
## 06/20/2018
1. [](#improved)
* Including EXIF JS library in the modules dependencies to fix orientation when uploading images
1. [](#bugfix)
* Fix HTML data template for checkboxes fields where 'use' property is "keys" [#258](https://github.com/getgrav/grav-plugin-form/pull/258)
# v2.15.0
## 05/31/2018
1. [](#new)
* Added support for `Uri::post()`
* Added support for `autocapitalize`, `inputmode`, and `spellcheck` options in field definitions
# v2.14.1
## 05/15/2018
1. [](#bugfix)
* Fixed regression with select field, causing issues with filepicker field [grav-plugin-admin#1441](https://github.com/getgrav/grav-plugin-admin/issues/1441)
# v2.14.0
## 05/11/2018
1. [](#new)
* Make `pagemedia` field available outside of pages context
* Added option on fields to disable displaying of label (`display_label: false`)
* Moved Dropzone HTML into an overridable Twig template
* Added support for image upload delete in Dropzone `file` field
1. [](#improved)
* Added support for `optgroup` within select field
* Save forms only once (stops extra work being done)
* Allow file field to pass dropzone options
* Added datasets support to fields
* Added `field.classes` support to display field
1. [](#bugfix)
* Removed overridden class in `password` field
* Worked around forms being lost if form cache expired before page cache, see [#240](https://github.com/getgrav/grav-plugin-form/pull/240)
* Fixed default form in dynamically created page if header uses `forms` instead of old `form` field
* Escape placeholder text in default field
# v2.13.3
## 04/13/2018
1. [](#new)
* Added support to save form data in raw format (yaml or json)
* Added new `timestamp` action to add a timestamp field
# v2.13.2
## 04/12/2018
1. [](#new)
* Added event `onFormPrepareValidation` to allow some pre-processing before form validation
* Added new `postfix` and `dateraw` options to "Save" action
1. [](#improved)
* Added support for `nest_id` boolean flag to `fieldset` field to nest sub-fields with name of fieldset
* Added classes attribute to `spacer` field
1. [](#bugfix)
* Fixed `Form::setFields()` causing validation to fail on added and removed fields
# v2.13.1
## 03/21/2018
1. [](#improved)
* CAPTCHA fallback to `cURL` if `Fopen` is not allowed [#224](https://github.com/getgrav/grav-plugin-form/pull/244)
* Use `visibility:hidden` rather than `display:none` for honeypot field [#235](https://github.com/getgrav/grav-plugin-form/pull/235)
* Added support for markdown in checkbox field [#233](https://github.com/getgrav/grav-plugin-form/pull/233)
* Added option to control `inline_css: true|false` for fields such as honeypot
* Added class and CSS for honeypot field
# v2.13.0
## 03/09/2018
1. [](#new)
* Forced registration of `Form` page template for admin
* Implemented support for `resolution` setting for images in file field
* Implemented support for `resizeWidth`, `resizeHeight`, `resizeQuality` and updated Dropzone to latest version
* Added a new `signature` field
1. [](#improved)
* Force an `onPageProcessed()` event if page cache expires before form cache [#240](https://github.com/getgrav/grav-plugin-form/pull/240)
1. [](#bugfix)
* Fixed an issue where unlimited size `0` was not being set properly in File field
* `field.description` now translated and displays properly
# v2.12.0
## 02/22/2018
1. [](#new)
* Added toggle to enable/disable client-side HTML5 validation
* Added toggle to enable/disable inline-error messages
1. [](#improved)
* Reformatted `form.php` plugin class for better readability
1. [](#bugfix)
* Fixed an issue with in-content Twig forms not working because forms were not initialized yet
# v2.11.5
## 02/16/2018
1. [](#new)
* Added support for `form: process: - call: ['Class', 'method']` for custom form handling
1. [](#bugfix)
* Fixed regression in v2.11.4: Call to a member function post() on null [grav#1720](https://github.com/getgrav/grav/issues/1720)
# v2.11.4
## 02/15/2018
1. [](#improved)
* Stopped Chrome from auto-completing admin user profile form [grav#1847](https://github.com/getgrav/grav/issues/1847)
* Start using composer to autoload classes
* Added support for `switch` to be treated as checkbox
1. [](#bugfix)
* Fixed missing form submit in dynamically created pages
# v2.11.3
## 01/31/2018
1. [](#new)
* Added support for `file` in **Display** field. Allows the ability to read a file and output it, works in combination with `|markdown` filter
* Added `minlength` and `maxlength` to **Textarea** field [#231](https://github.com/getgrav/grav-plugin-form/pull/231)
# v2.11.2
## 01/22/2018
1. [](#new)
* Added support for markdown in all form fields for `label`, `help`, and `description` when `markdown: true` is set on field
# v2.11.1
## 12/18/2017
1. [](#improved)
* Updated default fields to make them more consistent with class names
# v2.11.0
## 12/05/2017
1. [](#new)
* Added ability to set `novalidate: true` on form definition to turn off all HTML5 form validation
1. [](#improved)
* Improved logic to handle dynamically added forms to be more reliable
* Added Dutch Translation [#207](https://github.com/getgrav/grav-plugin-form/pull/207)
* Improved both HTML and JSON error output by utilizing `form.status`
* Code Cleanup
1. [](#bugfix)
* Fix AJAX response message and wrong status [#211](https://github.com/getgrav/grav-plugin-form/pull/211)
* Escaped YAML to form save action to prevent parsing errors [#206](https://github.com/getgrav/grav-plugin-form/pull/206)
* Fixed RU translations [#204](https://github.com/getgrav/grav-plugin-form/pull/204)
* Fixed nonce check fail not setting status to `error` [#213](https://github.com/getgrav/grav-plugin-form/issues/213)
* Fixed validation fail not setting status to `error` [#209](https://github.com/getgrav/grav-plugin-form/issues/209)
* Catch ValidationException to avoid potential fatal error
* Fixed regression issue on reset fields
* Removed `required` attribute in individual checkboxes as it forces all to be checked
* Security fix to ensure file uploads are not manipulated mid-post - thnx @FLH!
# v2.10.0
## 10/26/2017
1. [](#new)
* Added ability to 'remember' field values in cookie between submissions [#200](https://github.com/getgrav/grav-plugin-form/pull/200)
1. [](#improved)
* Added back improved `filesize` option that falls back to PHP file upload limits by default [#202](https://github.com/getgrav/grav-plugin-form/issues/202)
* Added missing file upload options into blueprints and language files
* Added the ability for a form to have an `http_response_code` and use it for `form-messages.html.twig` (requires Grav v1.3.6+)
# v2.9.3
## 10/11/2017
1. [](#improved)
* Removed `filesize` plugin configuration in favor of `system.media.upload_limit`
* Consolidated `field.classes` and `field.wrapper_classes` in radio/checkbox/checkboxes [#193](https://github.com/getgrav/grav-plugin-form/issues/)
* Remove trailing slash from form action [#195](https://github.com/getgrav/grav-plugin-form/issues/195)
* Improved `honeypot` validation check [#198](https://github.com/getgrav/grav-plugin-form/issues/198)
# v2.9.2
## 09/30/2017
1. [](#improved)
* Improved Polish translation
1. [](#bugfix)
* Added missing `@input: false` attributes to some non-display fields [#189](https://github.com/getgrav/grav-plugin-form/issues/189)
# v2.9.1
## 09/14/2017
1. [](#bugfix)
* Fixed backwards compatibility issue with conditional field [#188](https://github.com/getgrav/grav-plugin-form/pull/188)
# v2.9.0
## 09/07/2017
1. [](#new)
* Added **Refresh Prevention** capabilities (Not enabled by default) [#184](https://github.com/getgrav/grav-plugin-form/issues/184)
* Added support for field `attributes` [#176](https://github.com/getgrav/grav-plugin-form/pull/176)
* Added global variables for setting form classes
* Added support for new `select_optgroup` form field [#165](https://github.com/getgrav/grav-plugin-form/issues/165)
1. [](#improved)
* Moved messages output into partial to allow style overriding
* Logic cleanup
* Updated Italian and Russian translations
1. [](#bugfix)
* Fixed an issue with conditional field not always displaying properly
* Only add Twig form variable if not already set
* Fixed issue with multiple forms on a page failing on Captcha client-side validation [#182](https://github.com/getgrav/grav-plugin-form/issues/182)
* Fixed issue with Ajax forms return full form HTML on error [#163](https://github.com/getgrav/grav-plugin-form/issues/163)
# v2.8.2
## 08/18/2017
1. [](#new)
* Added new `columns` and `column` fields for controlled form layout
# v2.8.1
## 08/15/2017
1. [](#improved)
* Added extra class support to the default field for more flexible styling
# v2.8.0
## 07/16/2017
1. [](#bugfix)
* Fixed a typo in the spanish translation [#167](https://github.com/getgrav/grav-plugin-form/pull/167)
# v2.8.0-rc.2
## 06/22/2017
1. [](#improved)
* Add default client-side validation for captcha, with error popup [#139](https://github.com/getgrav/grav-plugin-form/issues/139)
* Added key observe for select
* Added Czech translation
1. [](#bugfix)
* Bug fix for radio type form field [#154](https://github.com/getgrav/grav-plugin-form/pull/154)
* Remove double escaping [#155](https://github.com/getgrav/grav-plugin-form/issues/154)
# v2.8.0-rc.1
## 05/22/2017
1. [](#new)
* Bundled as RC release for Grav/Admin RC releases
# v2.7.1
## 05/22/2017
1. [](#improved)
* Force modular sub-pages with forms to set `$never_cache_twig = true` to improve form processing reliability [#153](https://github.com/getgrav/grav-plugin-form/issues/153)
* Use new `Utils::getPagePathFromToken()` method
# v2.7.0
## 05/16/2017
1. [](#bugfix)
* Fix issue with dynamically added forms (Registration, Profile, Comments, etc) not processed [#149](https://github.com/getgrav/grav-plugin-form/issues/149)
* Fixed issue with nested values not being repopulated on form error [#140](https://github.com/getgrav/grav-plugin-form/issues/140)
# v2.6.0
## 05/04/2017
1. [](#new)
* Allow form item replacement in redirect location [#144](https://github.com/getgrav/grav-plugin-form/issues/144)
1. [](#bugfix)
* Fix regression with file uploads introduced in 2.5.0
# v2.5.0
## 04/24/2017
1. [](#new)
* Support proper form handling with nested fields [#141](https://github.com/getgrav/grav-plugin-form/issues/141)
1. [](#bugfix)
* Added check for valid Grav forms before trying to create a form object
# v2.4.0
## 04/19/2017
1. [](#new)
* Added the ability for front-end forms to use advanced blueprint features such as `data-*@` and `config-*@`
* Added support for dynamically added pages to process forms properly
* Added a new avatar field for displaying account avatar
* Added method to get all `data` from a form
* Support `task` in button types
1. [](#improved)
* Added `step` to range field [#136](https://github.com/getgrav/grav-plugin-form/issues/136)
* Added a new default ajax handler twig template
* Moved twig events to always process even if forms are not defined
* Some code cleanup
* Handle `null` with session-based form
* Added support for append/prepend to number field
1. [](#bugfix)
* Always process form events as long as a `$_POST` exists [login #101](https://github.com/getgrav/grav-plugin-login/issues/101)
* Various fixes for `file` field
* Allow manually added pages to process forms and upload files
* Fixed issue with nested fileds not showing up in `data.*.twig` templates
# v2.3.1
## 03/23/2017
1. [](#bugfix)
* Only include `outerclasses` DIV if defined [#135](https://github.com/getgrav/grav-plugin-form/issues/135)
# v2.3.0
## 03/17/2017
1. [](#new)
* Ability to process any form on any page via `action:`. Super useful if you want to handle form processing on some other non-form page (or Ajax)
* Added the ability for the form to set the `template:` to use to render the form processing response.
1. [](#bugfix)
* Fix `number` field so it works with min value `0` [#130](https://github.com/getgrav/grav-plugin-form/issues/130)
# v2.2.0
## 03/13/2017
1. [](#new)
* Added new `fieldset` form field [#125](https://github.com/getgrav/grav-plugin-form/issues/125)
* Added new `conditional form field` to show fields only if some `condition` is set
1. [](#improved)
* Added the option to have outer-classes on buttons [#124](https://github.com/getgrav/grav-plugin-form/issues/124)
* Added the option to disable fields label if not defined [#126](https://github.com/getgrav/grav-plugin-form/issues/126)
# v2.1.1
## 02/17/2017
1. [](#improved)
* Better default output for select, checkbox and checkboxes fields in the form destination page and in the emails sent via form submit [#121](https://github.com/getgrav/grav-plugin-form/issues/121)
# v2.1.0
## 02/10/2017
1. [](#improved)
* Reworked logic so form caching is based on `Pages::getPagesCacheId()`
* Added `url` option for button field
1. [](#bugfix)
* Fixed issue with `honeypot` field not throwing exception properly
# v2.0.10
## 02/08/2017
1. [](#improved)
* Optimistically set 'status' to `success` when requesting a form via Ajax. Form processing listeners should take care of setting status to something else
1. [](#bugfix)
* File uploads are now adding a `__form-file-uploader__` POST field to better allow identifying them with Ajax
* Require jQuery when using the File field, as it's needed by the form.min.js file required in the file upload functionality
# v2.0.9
## 01/24/2017
1. [](#bugfix)
* Translate the labels in data.html.twig [https://github.com/getgrav/grav-plugin-comments/issues/38](https://github.com/getgrav/grav-plugin-comments/issues/38)
* Fixed file input when `System` > `Twig` > `Autoescape` is set to `Yes`
# v2.0.8
## 12/13/2016
1. [](#new)
* RC released as stable
* Added a new `honeypot` field for form anti-spam protection
# v2.0.8-rc.1
## 11/26/2016
1. [](#bugfix)
* Fixed Forms 2.0 changes for registration form [#101](https://github.com/getgrav/grav-plugin-form/issues/101)
* Fixed errant reference to Grav DI container in Form#getPagePathFromToken [#105](https://github.com/getgrav/grav-plugin-form/issues/105)
* Fixed issue with spacer fields being displayed first, not in order [#104](https://github.com/getgrav/grav-plugin-form/issues/104)
# v2.0.7
## 11/17/2016
1. [](#improved)
* Added method to set all data in a form
* Added params to form action URL
* Added ability to add ids to buttons and to set them disabled
1. [](#bugfix)
* Moved Files Upload GC logic to function in front-end only
# v2.0.6
## 10/19/2016
1. [](#bugfix)
* Fixed translations for `display` field
* Fixed [#95](https://github.com/getgrav/grav-plugin-form/issues/95) multilanguage forms submission
* Fixed duplicate textarea class tag [#98](https://github.com/getgrav/grav-plugin-form/issues/98)
# v2.0.5
## 09/15/2016
1. [](#bugfix)
* Fix passing updating the header through event, no need for return value
# v2.0.4
## 09/15/2016
1. [](#improved)
* Allow filling the page header form dynamically (e.g. use case: Comments plugin)
# v2.0.3
## 09/12/2016
1. [](#improved)
* Use `Page::slug()` for form name if not set in the form itself (better backwards compatibility)
# v2.0.2
## 09/08/2016
1. [](#improved)
* Added support for Grav's autoescape twig setting
* Allow to add additional markup fields in form and field twig overrides
* Updated the french language translation
# v2.0.1
## 09/07/2016
1. [](#bugfix)
* Fixed a backwards compatibility issue with Admin forms
# v2.0.0
## 09/07/2016
1. [](#new)
* Forms now supports multiple forms per page!
* Access forms from any other page within the current page
* Instantiate forms directly in page content with Twig processing enabled
* New Twig function to get forms data from any other page
* Ability to use Twig in saved filename
* Reworked the `file` field. All files get uploaded via Ajax and are stored upon Submit. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB)
1. [](#improved)
* Added several missing HTML5 form input field types [#87](https://github.com/getgrav/grav-plugin-form/issues/87)
* Added Support for CSS id in form definition
# v1.3.2
## 08/10/2016
1. [](#improved)
* Added Romanian translation
1. [](#bugfix)
* Fixed an issue with Recaptcha secret throwing errors [#84](https://github.com/getgrav/grav-plugin-form/pull/84)
# v1.3.1
## 07/27/2016
1. [](#improved)
* Added support for multiple emails in `email` field (add `multiple: true` to enable)
1. [](#bugfix)
* Fixed backward incompatibility with forms submission and data retrieval [getgrav/grav#933](https://github.com/getgrav/grav/issues/933)
# v1.3.0
## 07/14/2016
1. [](#improved)
* When uploading a file through a form, if the file is already existing prepend the current day and time to the filename instead of overwriting it.
# v1.3.0-rc.4
## 06/21/2016
1. [](#bugfix)
* Fixed running on Grav 1.0.x
# v1.3.0-rc.3
## 06/17/2016
1. [](#new)
* Set hints for checkboxes options and allow field descriptions
# v1.3.0-rc.2
## 06/08/2016
1. [](#new)
* Allow to process Twig in a hidden field, by setting `evaluate: true`
# v1.3.0-rc.1
## 06/01/2016
1. [](#improved)
* French updated
# v1.3.0-beta.6
## 05/23/2016
1. [](#new)
* Added support for advanced blueprint functionality in forms
* Added site-wide form options to set Google Captcha site + secret keys [#34](https://github.com/getgrav/grav-plugin-form/pull/34)
* Session-based 'flash' storage of form for redirects [#48](https://github.com/getgrav/grav-plugin-form/issues/48)
* Added ability to **append** to file if you include a `process: save: body:` template attribute [#65](https://github.com/getgrav/grav-plugin-form/issues/65)
1. [](#improved)
* Support `keyname` form format like admin forms
* Added backwards compatibility for Captcha field
* Added 'markdown-notices' style output for better errors
* Added `Forms::getValue()` method to retrieve values programatically
* Changed `datetime` form field to simply extend `text` until implemented
* Updated french language
1. [](#bugfix)
* Refactored the files upload logic
* Missing Language string
* Fixed errors not getting output
# v1.3.0-beta.5
## 05/12/2016
1. [](#improved)
* Moved form/field.html.twig file to the default folder, to be more easily extended in themes
# v1.3.0-beta.4
## 05/04/2016
1. [](#new)
* Added support for `prepend` and `append` field attributes for Text input
# v1.3.0-beta.3
## 05/03/2016
1. [](#bugfix)
* Fix for select field admin translation
# v1.3.0-beta.2
## 04/27/2016
1. [](#bugfix)
* Fix for autoescape in spacer and display form fields
* Fix issue with form reset action [#66](https://github.com/getgrav/grav-plugin-form/pull/66)
# v1.3.0-beta.1
## 04/20/2016
1. [](#new)
* Added the HTML5 `range` input field with `min` and `max` parameters
1. [](#improved)
* Allow to override classes in Form definition for the form element
* Add more blocks in the Form twig template, so classes can be overridden more easily in themes
* Reworked some fields to fit the new Admin
* Use `scope` for form fields to allow fields to be excluded from the data by adding `input@: false` to their definition
* Added german translation
* Allow to add inline Twig to the form message definition
1. [](#bugfix)
* Fixed the form action URL for home page forms
* Fix stopping form events propagation, correctly stop when one event is stopped
* Allow to translate the fields placeholders and the form message
* Fix captcha javascript function ordering. Also, render it in the site active language
* Support attribute `for="id"` on label for checkbox
* Fix select fields with the multiple option enabled
* Fixed select options escaping with autoescape on - [#502](https://github.com/getgrav/grav-plugin-admin/issues/502)
# v1.2.2
## 02/11/2016
1. [](#bugfix)
* Fixed case issue when including form file.
# v1.2.1
## 02/11/2016
1. [](#new)
* Allow placeholder for **select** field
1. [](#improved)
* Use common language strings in blueprints
* Use `for` attribute in labels
* Improved `README.md`
* Code lint
1. [](#bugfix)
* Moved `nl2br` to correct place or will break for arrays
# v1.2.0
## 01/06/2016
1. [](#bugfix)
* Correctly merge the file field configuration
* restore full file information save
# v1.1.0
## 12/18/2015
1. [](#new)
* Croatian translation
* Added id, style, and disabled options to select fields
1. [](#improved)
* Allow adding form labels and help text as lang strings
* Allow translating field content
* Allow translating button and checkbox labels
* Allow adding classes to the form field container with `field.outerclasses`
* Updated French translation
1. [](#bugfix)
* Fixed error message on file upload
* Fixed overriding defaults for the file type in forms
# v1.0.3
## 12/11/2015
1. [](#improved)
* Updated languages
* Allow an action to stop processing
1. [](#bugfix)
* Fix captcha validation
* Fix issue where Form was unsetting valid page
# v1.0.2
## 12/01/2015
1. [](#bugfix)
* Fixed merge of defaults settings
* Support for arrays in `data.txt.twig`
* Fixed blueprint for admin
# v1.0.1
## 12/01/2015
1. [](#new)
* New **file upload** field
* Added modular form template
* Spanish translation
* Hungarian translation
* Italian translation
# v1.0.0
## 11/21/2015
1. [](#new)
* Server-side validation of forms #11
* Added french translation
* Added **nonce** form security
1. [](#improved)
* Show a more meaningful error when the display page is not found
* Added links to learn site for form examples
* Label can be omitted
* Allow user to set the CSS class for buttons
1. [](#bugfix)
* Fixed multi-language forms
* Checkbox is translatable
* Minor fixes
# v0.6.0
## 10/21/2015
1. [](#bugfix)
* Fixed for missing attributes in textarea field
* Fixed checkbox inputs
# v0.5.0
## 10/15/2015
1. [](#new)
* New `operation` param to allow different file saving strategies
* Ability to add new file saving strategies
* Now calls a `process()` method during form processing
1. [](#improved)
* Added server-side captcha validation and removed front-end validation
* Allow `filename` instead of `prefix`, `format` + `extension`
1. [](#bugfix)
* Fixed radio inputs
# v0.4.0
## 9/16/2015
1. [](#new)
* PHP server-side form validation
* Added new Google Catpcha field with front-end validation
1. [](#improved)
* Add defaults for forms, moved from the themes to the Form plugin
* Store multi-line fields with line endings converted to HTML
# v0.3.0
## 9/11/2015
1. [](#improved)
* Refactored all the forms fields
# v0.2.1
## 08/24/2015
1. [](#improved)
* Translated tooltips
# v0.2.0
## 08/11/2015
1. [](#improved)
* Disable `enable` in admin
# v0.1.0
## 08/04/2015
1. [](#new)
* ChangeLog started...
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Grav
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+29
View File
@@ -0,0 +1,29 @@
# Grav Form Plugin
The **form plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to create and use forms. This is currently used extensively by the **admin** and **login** plugins.
# Installation
The form plugin is easy to install with GPM.
```
$ bin/gpm install form
```
# Configuration
Simply copy the `user/plugins/form/form.yaml` into `user/config/plugins/form.yaml` and make your modifications.
```
enabled: true
```
# How to use the Form Plugin
The Learn site has two pages describing how to use the Form Plugin:
- [Forms](http://learn.getgrav.org/advanced/forms)
- [Add a contact form](http://learn.getgrav.org/forms/forms/example-form)
# Using email
Note: when using email functionality in your forms, make sure you have configured the Email plugin correctly. In particular, make sure you configured the "Email from" and "Email to" email addresses in the Email plugin with your email address
+194
View File
@@ -0,0 +1,194 @@
import $ from 'jquery';
import Sortable from 'sortablejs';
let body = $('body');
class Template {
constructor(container) {
this.container = $(container);
if (this.getName() === undefined) {
this.container = this.container.closest('[data-grav-array-name]');
}
}
getName() {
return this.container.data('grav-array-name') || '';
}
getKeyPlaceholder() {
return this.container.data('grav-array-keyname') || 'Key';
}
getValuePlaceholder() {
return this.container.data('grav-array-valuename') || 'Value';
}
isValueOnly() {
return this.container.find('[data-grav-array-mode="value_only"]:first').length || false;
}
isTextArea() {
return this.container.data('grav-array-textarea') || false;
}
shouldBeDisabled() {
// check for toggleables, if field is toggleable and it's not enabled, render disabled
let toggle = this.container.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
return toggle.length && toggle.is(':not(:checked)');
}
getNewRow() {
let tpl = '';
const value = this.isTextArea()
? `<textarea ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" name="" placeholder="${this.getValuePlaceholder()}"></textarea>`
: `<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" name="" value="" placeholder="${this.getValuePlaceholder()}" />`;
if (this.isValueOnly()) {
tpl += `
<div class="form-row array-field-value_only" data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
${value}
`;
} else {
tpl += `
<div class="form-row" data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="key" type="text" value="" placeholder="${this.getKeyPlaceholder()}" />
${value}
`;
}
tpl += `
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>`;
return tpl;
}
}
export default class ArrayField {
constructor() {
body.on('input', '[data-grav-array-type="key"], [data-grav-array-type="value"]', (event) => this.actionInput(event));
body.on('click touch', '[data-grav-array-action]:not([data-grav-array-action="sort"])', (event) => this.actionEvent(event));
this.arrays = $();
$('[data-grav-field="array"]').each((index, list) => this.addArray(list));
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
}
addArray(list) {
list = $(list);
list.find('[data-grav-array-type="container"]').each((index, container) => {
container = $(container);
if (container.data('array-sort') || container[0].hasAttribute('data-array-nosort')) { return; }
container.data('array-sort', new Sortable(container.get(0), {
handle: '.fa-bars',
animation: 150
}));
});
}
actionInput(event) {
let element = $(event.target);
let type = element.data('grav-array-type');
this._setTemplate(element);
let template = element.data('array-template');
let keyElement = type === 'key' ? element : element.siblings('[data-grav-array-type="key"]:first');
let valueElement = type === 'value' ? element : element.siblings('[data-grav-array-type="value"]:first');
let escaped_name = !template.isValueOnly() ? keyElement.val() : this.getIndexFor(element);
escaped_name = escaped_name.toString().replace(/\[/g, '%5B').replace(/]/g, '%5D');
let name = `${template.getName()}[${escaped_name}]`;
valueElement.attr('name', !valueElement.val() ? template.getName() : name);
this.refreshNames(template);
}
actionEvent(event) {
event && event.preventDefault();
let element = $(event.target);
let action = element.data('grav-array-action');
let container = element.parents('[data-grav-array-type="container"]');
this._setTemplate(element);
this[`${action}Action`](element);
let siblings = container.find('> div');
container[siblings.length > 1 ? 'removeClass' : 'addClass']('one-child');
}
addAction(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
row.after(template.getNewRow());
}
remAction(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
let isLast = !row.siblings().length;
if (isLast) {
let newRow = $(template.getNewRow());
row.after(newRow);
newRow.find('[data-grav-array-type="value"]:last').attr('name', template.getName());
}
row.remove();
this.refreshNames(template);
}
refreshNames(template) {
if (!template.isValueOnly()) { return; }
let row = template.container.find('> div > [data-grav-array-type="row"]');
let inputs = row.find('[name]:not([name=""])');
inputs.each((index, input) => {
input = $(input);
let name = input.attr('name');
name = name.replace(/\[\d+\]$/, `[${index}]`);
input.attr('name', name);
});
if (!inputs.length) {
row.find('[data-grav-array-type="value"]').attr('name', template.getName());
}
}
getIndexFor(element) {
let template = element.data('array-template');
let row = element.closest('[data-grav-array-type="row"]');
return template.container.find(`${template.isValueOnly() ? '> div ' : ''} > [data-grav-array-type="row"]`).index(row);
}
_setTemplate(element) {
if (!element.data('array-template')) {
element.data('array-template', new Template(element.closest('[data-grav-array-name]')));
}
}
_onAddedNodes(event, target/* , record, instance */) {
let arrays = $(target).find('[data-grav-field="array"]');
if (!arrays.length) { return; }
arrays.each((index, list) => {
list = $(list);
if (!~this.arrays.index(list)) {
this.addArray(list);
}
});
}
}
export let Instance = new ArrayField();
+340
View File
@@ -0,0 +1,340 @@
import $ from 'jquery';
import Dropzone from 'dropzone';
import EXIF from 'exif-js';
import {config, translations} from 'grav-form';
// translations
const Dictionary = {
dictCancelUpload: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD,
dictCancelUploadConfirmation: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD_CONFIRMATION,
dictDefaultMessage: translations.PLUGIN_FORM.DROPZONE_DEFAULT_MESSAGE,
dictFallbackMessage: translations.PLUGIN_FORM.DROPZONE_FALLBACK_MESSAGE,
dictFallbackText: translations.PLUGIN_FORM.DROPZONE_FALLBACK_TEXT,
dictFileTooBig: translations.PLUGIN_FORM.DROPZONE_FILE_TOO_BIG,
dictInvalidFileType: translations.PLUGIN_FORM.DROPZONE_INVALID_FILE_TYPE,
dictMaxFilesExceeded: translations.PLUGIN_FORM.DROPZONE_MAX_FILES_EXCEEDED,
dictRemoveFile: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE,
dictRemoveFileConfirmation: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE_CONFIRMATION,
dictResponseError: translations.PLUGIN_FORM.DROPZONE_RESPONSE_ERROR
};
Dropzone.autoDiscover = false;
const DropzoneMediaConfig = {
createImageThumbnails: {thumbnailWidth: 150},
addRemoveLinks: false,
dictDefaultMessage: Dictionary.dictDefaultMessage,
dictRemoveFileConfirmation: Dictionary.dictRemoveFileConfirmation,
previewTemplate: ''
};
window.EXIF = EXIF;
export default class FilesField {
constructor({container = '.dropzone.files-upload', options = {}} = {}) {
this.container = $(container);
if (!this.container.length) {
return;
}
this.urls = {};
DropzoneMediaConfig.previewTemplate = $('#dropzone-template').html();
this.options = Object.assign({}, Dictionary, DropzoneMediaConfig, {
klass: this,
url: this.container.data('file-url-add') || config.current_url,
acceptedFiles: this.container.data('media-types'),
init: this.initDropzone
}, this.container.data('dropzone-options'), options);
this.dropzone = new Dropzone(container, this.options);
this.dropzone.on('complete', this.onDropzoneComplete.bind(this));
this.dropzone.on('success', this.onDropzoneSuccess.bind(this));
this.dropzone.on('removedfile', this.onDropzoneRemovedFile.bind(this));
this.dropzone.on('sending', this.onDropzoneSending.bind(this));
this.dropzone.on('error', this.onDropzoneError.bind(this));
}
initDropzone() {
let files = this.options.klass.container.find('[data-file]');
let dropzone = this;
if (!files.length) {
return;
}
files.each((index, file) => {
file = $(file);
let data = file.data('file');
let mock = {
name: data.name,
size: data.size,
type: data.type,
status: Dropzone.ADDED,
accepted: true,
url: this.options.url,
removeUrl: data.remove,
data
};
dropzone.files.push(mock);
dropzone.options.addedfile.call(dropzone, mock);
if (mock.type.match(/^image\//)) dropzone.options.thumbnail.call(dropzone, mock, data.path);
file.remove();
});
}
getURI() {
return this.container.data('mediaUri') || '';
}
onDropzoneSending(file, xhr, formData) {
const form = this.container.closest('form');
const unique_id = form.find('[name="__unique_form_id__"]');
formData.append('__form-name__', form.find('[name="__form-name__"]').val());
if (unique_id.length) {
formData.append('__unique_form_id__', unique_id.val());
}
formData.append('__form-file-uploader__', 1);
formData.append('name', this.options.dotNotation);
formData.append('form-nonce', config.form_nonce);
formData.append('task', 'filesupload');
formData.append('uri', this.getURI());
}
onDropzoneSuccess(file, response, xhr) {
if (this.options.reloadPage) {
global.location.reload();
}
// store params for removing file from session before it gets saved
if (response.session) {
file.sessionParams = response.session;
file.removeUrl = this.options.url;
// Touch field value to force a mutation detection
const input = this.container.find('[name][type="hidden"]');
const value = input.val();
input.val(value + ' ');
}
return this.handleError({
file,
data: response,
mode: 'removeFile',
msg: `<p>${translations.PLUGIN_FORM.FILE_ERROR_UPLOAD} <strong>${file.name}</strong></p>
<pre>${response.message}</pre>`
});
}
onDropzoneComplete(file) {
if (!file.accepted && !file.rejected) {
let data = {
status: 'error',
message: `${translations.PLUGIN_FORM.FILE_UNSUPPORTED}: ${file.name.match(/\..+/).join('')}`
};
return this.handleError({
file,
data,
mode: 'removeFile',
msg: `<p>${translations.PLUGIN_FORM.FILE_ERROR_ADD} <strong>${file.name}</strong></p>
<pre>${data.message}</pre>`
});
}
if (this.options.reloadPage) {
global.location.reload();
}
}
onDropzoneRemovedFile(file, ...extra) {
if (!file.accepted || file.rejected) {
return;
}
const form = this.container.closest('form');
const unique_id = form.find('[name="__unique_form_id__"]');
let url = file.removeUrl || this.urls.delete || `${location.href}.json`;
let path = (url || '').match(/path:(.*)\//);
let data = new FormData();
data.append('filename', file.name);
data.append('__form-name__', form.find('[name="__form-name__"]').val());
data.append('name', this.options.dotNotation);
data.append('form-nonce', config.form_nonce);
data.append('uri', this.getURI());
if (file.sessionParams) {
data.append('__form-file-remover__', '1');
data.append('session', file.sessionParams);
}
if (unique_id.length) {
data.append('__unique_form_id__', unique_id.val());
}
$.ajax({
url,
data,
method: 'POST',
contentType: false,
processData: false,
success: () => {
if (!path) {
return;
}
path = global.atob(path[1]);
let input = this.container.find('[name][type="hidden"]');
let data = JSON.parse(input.val() || '{}');
delete data[path];
input.val(JSON.stringify(data));
}
});
}
onDropzoneError(file, response, xhr) {
let message = xhr && response.error ? response.error.message : response;
$(file.previewElement).find('[data-dz-errormessage]').html(message);
return this.handleError({
file,
data: {status: 'error'},
msg: `<pre>${message}</pre>`
});
}
handleError(options) {
return true;
/* let { file, data, mode, msg } = options;
if (data.status !== 'error' && data.status !== 'unauthorized') { return; }
switch (mode) {
case 'addBack':
if (file instanceof File) {
this.dropzone.addFile.call(this.dropzone, file);
} else {
this.dropzone.files.push(file);
this.dropzone.options.addedfile.call(this.dropzone, file);
this.dropzone.options.thumbnail.call(this.dropzone, file, file.extras.url);
}
break;
case 'removeFile':
default:
if (~this.dropzone.files.indexOf(file)) {
file.rejected = true;
this.dropzone.removeFile.call(this.dropzone, file, { silent: true });
}
break;
}
let modal = $('[data-remodal-id="generic"]');
modal.find('.error-content').html(msg);
$.remodal.lookup[modal.data('remodal')].open(); */
}
}
/*
export function UriToMarkdown(uri) {
uri = uri.replace(/@3x|@2x|@1x/, '');
uri = uri.replace(/\(/g, '%28');
uri = uri.replace(/\)/g, '%29');
return uri.match(/\.(jpe?g|png|gif|svg)$/i) ? `![](${uri})` : `[${decodeURI(uri)}](${uri})`;
}
*/
let instances = [];
let cache = $();
const onAddedNodes = (event, target/* , record, instance */) => {
let files = $(target).find('.dropzone.files-upload');
if (!files.length) {
return;
}
files.each((index, file) => {
file = $(file);
if (!~cache.index(file)) {
addNode(file);
}
});
};
const addNode = (container) => {
container = $(container);
let input = container.find('input[type="file"]');
let settings = container.data('grav-file-settings') || {};
if (settings.accept && ~settings.accept.indexOf('*')) {
settings.accept = [''];
}
let options = {
url: container.data('file-url-add') || (container.closest('form').attr('action') || config.current_url) + '.json',
paramName: settings.paramName || 'file',
dotNotation: settings.name || 'file',
acceptedFiles: settings.accept ? settings.accept.join(',') : input.attr('accept') || container.data('media-types'),
maxFilesize: settings.filesize || 256,
maxFiles: settings.limit || null,
resizeWidth: settings.resizeWidth || null,
resizeHeight: settings.resizeHeight || null,
resizeQuality: settings.resizeQuality || null,
accept: function(file, done) {
const resolution = settings.resolution;
let error = '';
if (!resolution) return done();
if ((this.options.maxFiles != null) && (this.getAcceptedFiles().length >= this.options.maxFiles)) {
done(this.options.dictMaxFilesExceeded.replace('{{maxFiles}}', this.options.maxFiles));
return this.emit('maxfilesexceeded', file);
}
const reader = new FileReader();
if (resolution.min || (!(settings.resizeWidth || settings.resizeHeight) && resolution.max)) {
reader.onload = function(event) {
const image = new Image();
image.src = event.target.result;
image.onload = function() {
if (resolution.min) {
Object.keys(resolution.min).forEach((attr) => {
if (this[attr] < resolution.min[attr]) {
error += translations.PLUGIN_FORM.RESOLUTION_MIN.replace(/{{attr}}/g, attr).replace(/{{min}}/g, resolution.min[attr]);
}
});
}
if (!(settings.resizeWidth || settings.resizeHeight)) {
if (resolution.max) {
Object.keys(resolution.max).forEach((attr) => {
if (this[attr] > resolution.max[attr]) {
error += translations.PLUGIN_FORM.RESOLUTION_MAX.replace(/{{attr}}/g, attr).replace(/{{max}}/g, resolution.max[attr]);
}
});
}
}
done(error);
};
};
reader.readAsDataURL(file);
} else {
return done(error);
}
}
};
cache = cache.add(container);
container = container[0];
instances.push(new FilesField({container, options}));
};
export let Instances = (() => {
$(document).ready(() => {
$('.dropzone.files-upload').each((i, container) => addNode(container));
$('body').on('mutation._grav', onAddedNodes);
});
return instances;
})();
+94
View File
@@ -0,0 +1,94 @@
import $ from 'jquery';
const attachToggleables = (form) => {
form = $(form);
let query = '[data-grav-field="toggleable"] input[type="checkbox"]';
form.on('change', query, (event) => {
let toggle = $(event.target);
let enabled = toggle.is(':checked');
let parent = toggle.closest('.form-field');
let label = parent.find('label.toggleable');
let fields = parent.find('.form-data');
let inputs = fields.find('input, select, textarea, button');
label.add(fields).css('opacity', enabled ? '' : 0.7);
inputs.map((index, input) => {
let isSelectize = input.selectize;
input = $(input);
if (isSelectize) {
isSelectize[enabled ? 'enable' : 'disable']();
} else {
input.prop('disabled', !enabled);
}
});
});
form.find(query).trigger('change');
};
const attachDisabledFields = (form) => {
form = $(form);
let prefix = '.form-field-toggleable .form-data';
let query = [];
['input', 'select', 'label[for]', 'textarea', '.selectize-control'].forEach((item) => {
query.push(`${prefix} ${item}`);
});
form.on('mousedown', query.join(', '), (event) => {
let input = $(event.target);
let isFor = input.prop('for');
let isSelectize = (input.hasClass('selectize-control') || input.parents('.selectize-control')).length;
if (isFor) { input = $(`[id="${isFor}"]`); }
if (isSelectize) { input = input.closest('.selectize-control').siblings('select[name]'); }
if (!input.prop('disabled')) { return true; }
let toggle = input.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
toggle.trigger('click');
});
};
/*
const submitUncheckedFields = (forms) => {
forms = $(forms);
let submitted = false;
forms.each((index, form) => {
form = $(form);
form.on('submit', () => {
// workaround for MS Edge, submitting multiple forms at the same time
if (submitted) { return false; }
let formId = form.attr('id');
let unchecked = form.find('input[type="checkbox"]:not(:checked):not(:disabled)');
let submit = form.find('[type="submit"]').add(`[form="${formId}"][type="submit"]`);
if (!unchecked.length) { return true; }
submit.addClass('pointer-events-disabled');
unchecked.each((index, element) => {
element = $(element);
let name = element.prop('name');
let fake = $(`<input type="hidden" name="${name}" value="0" />`);
form.append(fake);
});
submitted = true;
return true;
});
});
};
*/
$(document).ready(() => {
const forms = $('form').filter((form) => $(form).find('[name="__form-name__"]'));
if (!forms.length) { return; }
forms.each((index, form) => {
attachToggleables(form);
attachDisabledFields(form);
// submitUncheckedFields(form);
});
});
+7
View File
@@ -0,0 +1,7 @@
import './form';
import FileInstances from './file';
import ArrayInstances from './array';
import PageMedia, { Instance as PageMediaInstances } from './media';
import './tabs';
export default { FileInstances, ArrayInstances, Media: { PageMedia, PageMediaInstances } };
+174
View File
@@ -0,0 +1,174 @@
import $ from 'jquery';
import FilesField from './file';
import { config, translations } from 'grav-form';
import Sortable from 'sortablejs';
const template = `
<div class="dz-preview dz-file-preview">
<div class="dz-details">
<div class="dz-filename"><span data-dz-name></span></div>
<div class="dz-size" data-dz-size></div>
<img data-dz-thumbnail />
</div>
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
<div class="dz-success-mark"><span>✔</span></div>
<div class="dz-error-mark"><span>✘</span></div>
<div class="dz-error-message"><span data-dz-errormessage></span></div>
<a class="dz-remove" title="${translations.PLUGIN_FORM.DELETE}" href="javascript:undefined;" data-dz-remove>${translations.PLUGIN_FORM.DELETE}</a>
</div>`.trim();
export default class PageMedia extends FilesField {
constructor({ container = '#grav-dropzone', options = {} } = {}) {
const previewTemplate = $('#dropzone-media-template').html() || template;
options = Object.assign(options, { previewTemplate });
super({ container, options });
if (!this.container.length) { return; }
this.urls = {
fetch: `${this.container.data('media-url')}/task${config.param_sep}listmedia`,
add: `${this.container.data('media-url')}/task${config.param_sep}addmedia`,
delete: `${this.container.data('media-url')}/task${config.param_sep}delmedia`
};
this.dropzone.options.url = this.urls.add;
if (typeof this.options.fetchMedia === 'undefined' || this.options.fetchMedia) {
this.fetchMedia();
}
const field = $(`[name="${this.container.data('dropzone-field')}"]`);
if (field.length) {
this.sortable = new Sortable(this.container.get(0), {
animation: 150,
// forceFallback: true,
setData: (dataTransfer, target) => {
target = $(target);
this.dropzone.disable();
target.addClass('hide-backface');
dataTransfer.effectAllowed = 'copy';
},
onSort: () => {
let names = [];
this.container.find('[data-dz-name]').each((index, file) => {
file = $(file);
const name = file.text().trim();
names.push(name);
});
field.val(names.join(','));
}
});
}
}
onDropzoneRemovedFile(file, ...extra) {
if (!file.accepted || file.rejected) { return; }
const form = this.container.closest('form');
const unique_id = form.find('[name="__unique_form_id__"]');
let url = file.removeUrl || this.urls.delete || `${location.href}.json`;
let path = (url || '').match(/path:(.*)\//);
let data = new FormData();
data.append('filename', file.name);
data.append('__form-name__', form.find('[name="__form-name__"]').val());
if (unique_id.length) {
data.append('__unique_form_id__', unique_id.val());
}
data.append('name', this.options.dotNotation);
data.append('form-nonce', config.form_nonce);
if (file.sessionParams) {
data.append('__form-file-remover__', '1');
data.append('session', file.sessionParams);
}
$.ajax({
url,
data,
method: 'POST',
contentType: false,
processData: false,
success: () => {
if (!path) { return; }
path = global.atob(path[1]);
let input = this.container.find('[name][type="hidden"]');
let data = JSON.parse(input.val() || '{}');
delete data[path];
input.val(JSON.stringify(data));
}
});
}
fetchMedia() {
const order = this.container.closest('.form-field').find('[name="data[header][media_order]"]').val();
const data = { order };
let url = this.urls.fetch;
$.ajax({
url,
method: 'POST',
data,
success: (response) => {
if (typeof response === 'string' || response instanceof String) {
return false;
}
response = response.results;
Object.keys(response).forEach((name) => {
let data = response[name];
let mock = { name, size: data.size, accepted: true, extras: data };
this.dropzone.files.push(mock);
this.dropzone.options.addedfile.call(this.dropzone, mock);
this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
});
this.container.find('.dz-preview').prop('draggable', 'true');
}
});
/*
request(url, { method: 'post', body }, (response) => {
let results = response.results;
Object.keys(results).forEach((name) => {
let data = results[name];
let mock = { name, size: data.size, accepted: true, extras: data };
this.dropzone.files.push(mock);
this.dropzone.options.addedfile.call(this.dropzone, mock);
this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
});
this.container.find('.dz-preview').prop('draggable', 'true');
});*/
}
onDropzoneSending(file, xhr, formData) {
/*
// Cannot call super because Safari and IE API don't implement `delete`
super.onDropzoneSending(file, xhr, formData);
formData.delete('task');
*/
formData.append('name', this.options.dotNotation);
formData.append('admin-nonce', config.admin_nonce);
}
onDropzoneComplete(file) {
super.onDropzoneComplete(file);
this.sortable.options.onSort();
// accepted
$('.dz-preview').prop('draggable', 'true');
}
onDropzoneRemovedFile(file, ...extra) {
super.onDropzoneRemovedFile(file, ...extra);
this.sortable.options.onSort();
}
}
export let Instance = new PageMedia();
+14
View File
@@ -0,0 +1,14 @@
import $ from 'jquery';
$('body').on('touchstart click', '[data-tabid]', (event) => {
event && event.stopPropagation();
let target = $(event.currentTarget);
const panel = $(`[id="${target.data('tabid')}"]`);
target.siblings('[data-tabid]').removeClass('active');
target.addClass('active');
panel.siblings('[id]').removeClass('active');
panel.addClass('active');
});
+4
View File
@@ -0,0 +1,4 @@
import Instances from './fields';
import './utils/keep-alive';
export { Instances };
+15
View File
@@ -0,0 +1,15 @@
import $ from 'jquery';
import {config} from 'grav-form';
$(document).ready(() => {
const keepAlive = $('[data-grav-keepalive]');
if (keepAlive.length) {
const timeout = config.session_timeout / 1.5 * 1000;
setInterval(() => {
$.ajax({
url: `${config.base_url_relative}/task${config.param_sep}keep-alive`
});
}, timeout);
}
});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.form-group.has-errors{margin:0 -5px;padding:0 5px;border:1px solid rgba(255,0,0,.2);border-radius:3px;background:rgba(255,0,0,.05)}.form-errors{color:#b52b27}.form-honeybear{position:absolute!important;visibility:hidden;overflow:hidden;clip:rect(1px,1px,1px,1px);width:1px;height:1px}.form-errors p{margin:0}.form-input-file input{display:none}.form-input-file .dz-default.dz-message{position:absolute;top:50%;right:0;left:0;margin:0;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center}.form-input-file.dropzone{position:relative;min-height:70px;margin-bottom:.85rem;padding:.5rem;color:#aaa;border:2px dashed #ccc;border-radius:3px}.form-input-file.dropzone .dz-preview{margin:.5rem}.form-input-file.dropzone .dz-preview:hover{z-index:2}.form-input-file.dropzone .dz-preview .dz-error-message{width:auto;min-width:140px}.form-input-file.dropzone .dz-preview .dz-image,.form-input-file.dropzone .dz-preview.dz-file-preview .dz-image{z-index:1;border-radius:3px}.form-tabs .tabs-nav{display:flex;margin-bottom:-1px;padding-top:1px}.form-tabs .tabs-nav a{display:flex;padding:10px;cursor:pointer;transition:color .5s ease,background .5s ease;text-align:center;border-bottom:1px solid #eee;border-radius:5px 5px 0 0;flex:1;align-items:center;justify-content:center}.form-tabs .tabs-nav a.active{margin:0 -1px;border:1px solid #eee;border-bottom:1px solid transparent}.form-tabs .tabs-nav a.active span{color:#000}.form-tabs .tabs-nav span{line-height:1.1;display:inline-block}.form-tabs.subtle .tabs-nav{margin-right:0!important}.form-tabs .tabs-content .tab__content{display:none;padding-top:2rem}.form-tabs .tabs-content .tab__content.active{display:block}.checkboxes{display:inline-block}.checkboxes label{position:relative;display:inline;margin-right:15px;padding:0 0 0 20px;cursor:pointer}.checkboxes label:before{position:absolute;left:0;display:inline-block;width:20px;height:20px;margin-top:0;margin-right:10px;content:'';border:1px solid #e6e6e6;border-radius:3px}.checkboxes input[type=checkbox]{display:none}.checkboxes input[type=checkbox]:checked+label:before{font-size:20px;line-height:1;content:'\2713';text-align:center}.checkboxes.toggleable label{margin-right:0}.form-field-toggleable .checkboxes.toggleable{margin-right:5px;vertical-align:middle}.form-field-toggleable .checkboxes+label{display:inline-block}.switch-toggle{line-height:35px;display:inline-flex;overflow:hidden;border:1px solid #eee;border-radius:3px}.switch-toggle input[type=radio]{position:absolute;display:none;visibility:hidden}.switch-toggle label{display:inline-block;margin:0;padding:0 15px;cursor:pointer;transition:background-color .5s ease;white-space:nowrap;color:inherit}.switch-toggle input.highlight:checked+label{color:#fff;background:#333}.switch-toggle input:checked+label{color:#fff;background:#999}.signature-pad{font-size:10px;position:relative;display:flex;flex-direction:column;width:100%;max-width:700px;height:100%;max-height:460px;padding:16px;border:1px solid #f0f0f0;background-color:#fff}.signature-pad--body{position:relative;min-height:100px;border:1px solid #f6f6f6;flex:1}.signature-pad--body canvas{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:4px;box-shadow:0 0 5px rgba(0,0,0,.02) inset}.signature-pad--footer{font-size:1.2em;text-align:center;color:#c3c3c3}.signature-pad--actions{display:flex;margin-top:8px;justify-content:space-between}[data-grav-field=array] .form-row{display:flex;margin-bottom:.5rem;align-items:center}[data-grav-field=array] .form-row>input,[data-grav-field=array] .form-row>textarea{display:inline-block;margin:0 .5rem}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
if (typeof Object.assign !== 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, 'assign', {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
+617
View File
@@ -0,0 +1,617 @@
/*!
* Signature Pad v2.3.2
* https://github.com/szimek/signature_pad
*
* Copyright 2017 Szymon Nowak
* Released under the MIT license
*
* The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
* http://corner.squareup.com/2012/07/smoother-signatures.html
*
* Implementation of interpolation using cubic Bézier curves is taken from:
* http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
*
* Algorithm for approximated length of a Bézier curve is taken from:
* http://www.lemoda.net/maths/bezier-length/index.html
*
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.SignaturePad = factory());
}(this, (function () { 'use strict';
function Point(x, y, time) {
this.x = x;
this.y = y;
this.time = time || new Date().getTime();
}
Point.prototype.velocityFrom = function (start) {
return this.time !== start.time ? this.distanceTo(start) / (this.time - start.time) : 1;
};
Point.prototype.distanceTo = function (start) {
return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
};
Point.prototype.equals = function (other) {
return this.x === other.x && this.y === other.y && this.time === other.time;
};
function Bezier(startPoint, control1, control2, endPoint) {
this.startPoint = startPoint;
this.control1 = control1;
this.control2 = control2;
this.endPoint = endPoint;
}
// Returns approximated length.
Bezier.prototype.length = function () {
var steps = 10;
var length = 0;
var px = void 0;
var py = void 0;
for (var i = 0; i <= steps; i += 1) {
var t = i / steps;
var cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
var cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
if (i > 0) {
var xdiff = cx - px;
var ydiff = cy - py;
length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
}
px = cx;
py = cy;
}
return length;
};
/* eslint-disable no-multi-spaces, space-in-parens */
Bezier.prototype._point = function (t, start, c1, c2, end) {
return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + 3.0 * c2 * (1.0 - t) * t * t + end * t * t * t;
};
/* eslint-disable */
// http://stackoverflow.com/a/27078401/815507
function throttle(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function later() {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function () {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
function SignaturePad(canvas, options) {
var self = this;
var opts = options || {};
this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
this.minWidth = opts.minWidth || 0.5;
this.maxWidth = opts.maxWidth || 2.5;
this.throttle = 'throttle' in opts ? opts.throttle : 16; // in miliseconds
this.minDistance = 'minDistance' in opts ? opts.minDistance : 5;
if (this.throttle) {
this._strokeMoveUpdate = throttle(SignaturePad.prototype._strokeUpdate, this.throttle);
} else {
this._strokeMoveUpdate = SignaturePad.prototype._strokeUpdate;
}
this.dotSize = opts.dotSize || function () {
return (this.minWidth + this.maxWidth) / 2;
};
this.penColor = opts.penColor || 'black';
this.backgroundColor = opts.backgroundColor || 'rgba(0,0,0,0)';
this.onBegin = opts.onBegin;
this.onEnd = opts.onEnd;
this._canvas = canvas;
this._ctx = canvas.getContext('2d');
this.clear();
// We need add these inline so they are available to unbind while still having
// access to 'self' we could use _.bind but it's not worth adding a dependency.
this._handleMouseDown = function (event) {
if (event.which === 1) {
self._mouseButtonDown = true;
self._strokeBegin(event);
}
};
this._handleMouseMove = function (event) {
if (self._mouseButtonDown) {
self._strokeMoveUpdate(event);
}
};
this._handleMouseUp = function (event) {
if (event.which === 1 && self._mouseButtonDown) {
self._mouseButtonDown = false;
self._strokeEnd(event);
}
};
this._handleTouchStart = function (event) {
// Prevent scrolling.
event.preventDefault();
if (event.targetTouches.length === 1) {
var touch = event.changedTouches[0];
self._strokeBegin(touch);
}
};
this._handleTouchMove = function (event) {
// Prevent scrolling.
event.preventDefault();
var touch = event.targetTouches[0];
self._strokeMoveUpdate(touch);
};
this._handleTouchEnd = function (event) {
var wasCanvasTouched = event.target === self._canvas;
if (wasCanvasTouched) {
event.preventDefault();
self._strokeEnd(event);
}
};
// Enable mouse and touch event handlers
this.on();
}
// Public methods
SignaturePad.prototype.clear = function () {
var ctx = this._ctx;
var canvas = this._canvas;
ctx.fillStyle = this.backgroundColor;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, canvas.width, canvas.height);
this._data = [];
this._reset();
this._isEmpty = true;
};
SignaturePad.prototype.fromDataURL = function (dataUrl) {
var _this = this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var image = new Image();
var ratio = options.ratio || window.devicePixelRatio || 1;
var width = options.width || this._canvas.width / ratio;
var height = options.height || this._canvas.height / ratio;
this._reset();
image.src = dataUrl;
image.onload = function () {
_this._ctx.drawImage(image, 0, 0, width, height);
};
this._isEmpty = false;
};
SignaturePad.prototype.toDataURL = function (type) {
var _canvas;
switch (type) {
case 'image/svg+xml':
return this._toSVG();
default:
for (var _len = arguments.length, options = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
options[_key - 1] = arguments[_key];
}
return (_canvas = this._canvas).toDataURL.apply(_canvas, [type].concat(options));
}
};
SignaturePad.prototype.on = function () {
this._handleMouseEvents();
this._handleTouchEvents();
};
SignaturePad.prototype.off = function () {
// Pass touch events to canvas element on mobile IE11 and Edge.
this._canvas.style.msTouchAction = 'auto';
this._canvas.style.touchAction = 'auto';
this._canvas.removeEventListener('mousedown', this._handleMouseDown);
this._canvas.removeEventListener('mousemove', this._handleMouseMove);
document.removeEventListener('mouseup', this._handleMouseUp);
this._canvas.removeEventListener('touchstart', this._handleTouchStart);
this._canvas.removeEventListener('touchmove', this._handleTouchMove);
this._canvas.removeEventListener('touchend', this._handleTouchEnd);
};
SignaturePad.prototype.isEmpty = function () {
return this._isEmpty;
};
// Private methods
SignaturePad.prototype._strokeBegin = function (event) {
this._data.push([]);
this._reset();
this._strokeUpdate(event);
if (typeof this.onBegin === 'function') {
this.onBegin(event);
}
};
SignaturePad.prototype._strokeUpdate = function (event) {
var x = event.clientX;
var y = event.clientY;
var point = this._createPoint(x, y);
var lastPointGroup = this._data[this._data.length - 1];
var lastPoint = lastPointGroup && lastPointGroup[lastPointGroup.length - 1];
var isLastPointTooClose = lastPoint && point.distanceTo(lastPoint) < this.minDistance;
// Skip this point if it's too close to the previous one
if (!(lastPoint && isLastPointTooClose)) {
var _addPoint = this._addPoint(point),
curve = _addPoint.curve,
widths = _addPoint.widths;
if (curve && widths) {
this._drawCurve(curve, widths.start, widths.end);
}
this._data[this._data.length - 1].push({
x: point.x,
y: point.y,
time: point.time,
color: this.penColor
});
}
};
SignaturePad.prototype._strokeEnd = function (event) {
var canDrawCurve = this.points.length > 2;
var point = this.points[0]; // Point instance
if (!canDrawCurve && point) {
this._drawDot(point);
}
if (point) {
var lastPointGroup = this._data[this._data.length - 1];
var lastPoint = lastPointGroup[lastPointGroup.length - 1]; // plain object
// When drawing a dot, there's only one point in a group, so without this check
// such group would end up with exactly the same 2 points.
if (!point.equals(lastPoint)) {
lastPointGroup.push({
x: point.x,
y: point.y,
time: point.time,
color: this.penColor
});
}
}
if (typeof this.onEnd === 'function') {
this.onEnd(event);
}
};
SignaturePad.prototype._handleMouseEvents = function () {
this._mouseButtonDown = false;
this._canvas.addEventListener('mousedown', this._handleMouseDown);
this._canvas.addEventListener('mousemove', this._handleMouseMove);
document.addEventListener('mouseup', this._handleMouseUp);
};
SignaturePad.prototype._handleTouchEvents = function () {
// Pass touch events to canvas element on mobile IE11 and Edge.
this._canvas.style.msTouchAction = 'none';
this._canvas.style.touchAction = 'none';
this._canvas.addEventListener('touchstart', this._handleTouchStart);
this._canvas.addEventListener('touchmove', this._handleTouchMove);
this._canvas.addEventListener('touchend', this._handleTouchEnd);
};
SignaturePad.prototype._reset = function () {
this.points = [];
this._lastVelocity = 0;
this._lastWidth = (this.minWidth + this.maxWidth) / 2;
this._ctx.fillStyle = this.penColor;
};
SignaturePad.prototype._createPoint = function (x, y, time) {
var rect = this._canvas.getBoundingClientRect();
return new Point(x - rect.left, y - rect.top, time || new Date().getTime());
};
SignaturePad.prototype._addPoint = function (point) {
var points = this.points;
var tmp = void 0;
points.push(point);
if (points.length > 2) {
// To reduce the initial lag make it work with 3 points
// by copying the first point to the beginning.
if (points.length === 3) points.unshift(points[0]);
tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
var c2 = tmp.c2;
tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
var c3 = tmp.c1;
var curve = new Bezier(points[1], c2, c3, points[2]);
var widths = this._calculateCurveWidths(curve);
// Remove the first element from the list,
// so that we always have no more than 4 points in points array.
points.shift();
return { curve: curve, widths: widths };
}
return {};
};
SignaturePad.prototype._calculateCurveControlPoints = function (s1, s2, s3) {
var dx1 = s1.x - s2.x;
var dy1 = s1.y - s2.y;
var dx2 = s2.x - s3.x;
var dy2 = s2.y - s3.y;
var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
var dxm = m1.x - m2.x;
var dym = m1.y - m2.y;
var k = l2 / (l1 + l2);
var cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
var tx = s2.x - cm.x;
var ty = s2.y - cm.y;
return {
c1: new Point(m1.x + tx, m1.y + ty),
c2: new Point(m2.x + tx, m2.y + ty)
};
};
SignaturePad.prototype._calculateCurveWidths = function (curve) {
var startPoint = curve.startPoint;
var endPoint = curve.endPoint;
var widths = { start: null, end: null };
var velocity = this.velocityFilterWeight * endPoint.velocityFrom(startPoint) + (1 - this.velocityFilterWeight) * this._lastVelocity;
var newWidth = this._strokeWidth(velocity);
widths.start = this._lastWidth;
widths.end = newWidth;
this._lastVelocity = velocity;
this._lastWidth = newWidth;
return widths;
};
SignaturePad.prototype._strokeWidth = function (velocity) {
return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
};
SignaturePad.prototype._drawPoint = function (x, y, size) {
var ctx = this._ctx;
ctx.moveTo(x, y);
ctx.arc(x, y, size, 0, 2 * Math.PI, false);
this._isEmpty = false;
};
SignaturePad.prototype._drawCurve = function (curve, startWidth, endWidth) {
var ctx = this._ctx;
var widthDelta = endWidth - startWidth;
var drawSteps = Math.floor(curve.length());
ctx.beginPath();
for (var i = 0; i < drawSteps; i += 1) {
// Calculate the Bezier (x, y) coordinate for this step.
var t = i / drawSteps;
var tt = t * t;
var ttt = tt * t;
var u = 1 - t;
var uu = u * u;
var uuu = uu * u;
var x = uuu * curve.startPoint.x;
x += 3 * uu * t * curve.control1.x;
x += 3 * u * tt * curve.control2.x;
x += ttt * curve.endPoint.x;
var y = uuu * curve.startPoint.y;
y += 3 * uu * t * curve.control1.y;
y += 3 * u * tt * curve.control2.y;
y += ttt * curve.endPoint.y;
var width = startWidth + ttt * widthDelta;
this._drawPoint(x, y, width);
}
ctx.closePath();
ctx.fill();
};
SignaturePad.prototype._drawDot = function (point) {
var ctx = this._ctx;
var width = typeof this.dotSize === 'function' ? this.dotSize() : this.dotSize;
ctx.beginPath();
this._drawPoint(point.x, point.y, width);
ctx.closePath();
ctx.fill();
};
SignaturePad.prototype._fromData = function (pointGroups, drawCurve, drawDot) {
for (var i = 0; i < pointGroups.length; i += 1) {
var group = pointGroups[i];
if (group.length > 1) {
for (var j = 0; j < group.length; j += 1) {
var rawPoint = group[j];
var point = new Point(rawPoint.x, rawPoint.y, rawPoint.time);
var color = rawPoint.color;
if (j === 0) {
// First point in a group. Nothing to draw yet.
// All points in the group have the same color, so it's enough to set
// penColor just at the beginning.
this.penColor = color;
this._reset();
this._addPoint(point);
} else if (j !== group.length - 1) {
// Middle point in a group.
var _addPoint2 = this._addPoint(point),
curve = _addPoint2.curve,
widths = _addPoint2.widths;
if (curve && widths) {
drawCurve(curve, widths, color);
}
} else {
// Last point in a group. Do nothing.
}
}
} else {
this._reset();
var _rawPoint = group[0];
drawDot(_rawPoint);
}
}
};
SignaturePad.prototype._toSVG = function () {
var _this2 = this;
var pointGroups = this._data;
var canvas = this._canvas;
var ratio = Math.max(window.devicePixelRatio || 1, 1);
var minX = 0;
var minY = 0;
var maxX = canvas.width / ratio;
var maxY = canvas.height / ratio;
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttributeNS(null, 'width', canvas.width);
svg.setAttributeNS(null, 'height', canvas.height);
this._fromData(pointGroups, function (curve, widths, color) {
var path = document.createElement('path');
// Need to check curve for NaN values, these pop up when drawing
// lines on the canvas that are not continuous. E.g. Sharp corners
// or stopping mid-stroke and than continuing without lifting mouse.
if (!isNaN(curve.control1.x) && !isNaN(curve.control1.y) && !isNaN(curve.control2.x) && !isNaN(curve.control2.y)) {
var attr = 'M ' + curve.startPoint.x.toFixed(3) + ',' + curve.startPoint.y.toFixed(3) + ' ' + ('C ' + curve.control1.x.toFixed(3) + ',' + curve.control1.y.toFixed(3) + ' ') + (curve.control2.x.toFixed(3) + ',' + curve.control2.y.toFixed(3) + ' ') + (curve.endPoint.x.toFixed(3) + ',' + curve.endPoint.y.toFixed(3));
path.setAttribute('d', attr);
path.setAttribute('stroke-width', (widths.end * 2.25).toFixed(3));
path.setAttribute('stroke', color);
path.setAttribute('fill', 'none');
path.setAttribute('stroke-linecap', 'round');
svg.appendChild(path);
}
}, function (rawPoint) {
var circle = document.createElement('circle');
var dotSize = typeof _this2.dotSize === 'function' ? _this2.dotSize() : _this2.dotSize;
circle.setAttribute('r', dotSize);
circle.setAttribute('cx', rawPoint.x);
circle.setAttribute('cy', rawPoint.y);
circle.setAttribute('fill', rawPoint.color);
svg.appendChild(circle);
});
var prefix = 'data:image/svg+xml;base64,';
var header = '<svg' + ' xmlns="http://www.w3.org/2000/svg"' + ' xmlns:xlink="http://www.w3.org/1999/xlink"' + (' viewBox="' + minX + ' ' + minY + ' ' + maxX + ' ' + maxY + '"') + (' width="' + maxX + '"') + (' height="' + maxY + '"') + '>';
var body = svg.innerHTML;
// IE hack for missing innerHTML property on SVGElement
if (body === undefined) {
var dummy = document.createElement('dummy');
var nodes = svg.childNodes;
dummy.innerHTML = '';
for (var i = 0; i < nodes.length; i += 1) {
dummy.appendChild(nodes[i].cloneNode(true));
}
body = dummy.innerHTML;
}
var footer = '</svg>';
var data = header + body + footer;
return prefix + btoa(data);
};
SignaturePad.prototype.fromData = function (pointGroups) {
var _this3 = this;
this.clear();
this._fromData(pointGroups, function (curve, widths) {
return _this3._drawCurve(curve, widths.start, widths.end);
}, function (rawPoint) {
return _this3._drawDot(rawPoint);
});
this._data = pointGroups;
};
SignaturePad.prototype.toData = function () {
return this._data;
};
return SignaturePad;
})));
+197
View File
@@ -0,0 +1,197 @@
name: Form
version: 3.0.9
testing: false
description: Enables the forms handling
icon: check-square
author:
name: Team Grav
email: devs@getgrav.org
url: http://getgrav.org
keywords: plugin, form
homepage: https://github.com/getgrav/grav-plugin-form
bugs: https://github.com/getgrav/grav-plugin-form/issues
license: MIT
dependencies:
- { name: grav, version: '>=1.6.0' }
form:
validation: strict
fields:
enabled:
type: hidden
label: PLUGIN_ADMIN.PLUGIN_STATUS
highlight: 1
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
general:
type: section
title: PLUGIN_FORM.GENERAL
fields:
built_in_css:
type: toggle
label: PLUGIN_FORM.USE_BUILT_IN_CSS
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
inline_css:
type: toggle
label: PLUGIN_FORM.USE_INLINE_CSS
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
refresh_prevention:
type: toggle
label: PLUGIN_FORM.REFRESH_PREVENTION
help: PLUGIN_FORM.REFRESH_PREVENTION_HELP
highlight: 1
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
client_side_validation:
type: toggle
label: PLUGIN_FORM.CLIENT_SIDE_VALIDATION
help: PLUGIN_FORM.CLIENT_SIDE_VALIDATION_HELP
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
inline_errors:
type: toggle
label: PLUGIN_FORM.INLINE_ERRORS
help: PLUGIN_FORM.INLINE_ERRORS_HELP
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
files:
type: section
title: PLUGIN_FORM.FILES
fields:
files.multiple:
type: toggle
label: PLUGIN_FORM.ALLOW_MULTIPLE
help: PLUGIN_FORM.ALLOW_MULTIPLE_HELP
highlight: 1
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
files.limit:
type: text
size: x-small
label: PLUGIN_FORM.LIMIT
help: PLUGIN_FORM.LIMIT_HELP
default: 10
validate:
type: number
min: 1
files.destination:
type: text
size: large
label: PLUGIN_FORM.DESTINATION
help: PLUGIN_FORM.DESTINATION_HELP
default: '@self'
files.accept:
type: selectize
size: large
label: PLUGIN_FORM.ACCEPT
help: PLUGIN_FORM.ACCEPT_HELP
classes: fancy
default:
- image/*
validate:
type: commalist
files.filesize:
type: text
label: PLUGIN_FORM.FILESIZE
help: PLUGIN_FORM.FILESIZE_HELP
size: x-small
default: 5
validate:
type: number
min: 0
files.avoid_overwriting:
type: toggle
label: PLUGIN_FORM.AVOID_OVERWRITING
help: PLUGIN_FORM.AVOID_OVERWRITING_HELP
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
files.random_name:
type: toggle
label: PLUGIN_FORM.RANDOM_NAME
help: PLUGIN_FORM.RANDOM_NAME_HELP
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
recaptcha:
type: section
title: PLUGIN_FORM.RECAPTCHA
fields:
recaptcha.version:
type: select
label: PLUGIN_FORM.RECAPTCHA_VERSION
default: 2-checkbox
options:
2-checkbox: PLUGIN_FORM.RECAPTCHA_VERSION_V2_CHECKBOX
2-invisible: PLUGIN_FORM.RECAPTCHA_VERSION_V2_INVISIBLE
3: PLUGIN_FORM.RECAPTCHA_VERSION_V3_LATEST
recaptcha.theme:
type: select
label: PLUGIN_FORM.RECAPTCHA_THEME
default: light
options:
light: PLUGIN_FORM.RECAPTCHA_THEME_LIGHT
dark: PLUGIN_FORM.RECAPTCHA_THEME_DARK
recaptcha.site_key:
recaptcha.site_key:
type: text
label: PLUGIN_FORM.RECAPTCHA_SITE_KEY
help: PLUGIN_FORM.RECAPTCHA_SITE_KEY_HELP
default: ''
recaptcha.secret_key:
type: text
label: PLUGIN_FORM.RECAPTCHA_SECRET_KEY
help: PLUGIN_FORM.RECAPTCHA_SECRET_KEY_HELP
default: ''
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Grav\Plugin\Form;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Page;
use Grav\Framework\Form\Interfaces\FormFactoryInterface;
use Grav\Framework\Form\Interfaces\FormInterface;
class FormFactory implements FormFactoryInterface
{
/**
* Create form using the header of the page.
*
* @param Page $page
* @param string $name
* @param array $form
* @return Form|null
* @deprecated 1.6 Use FormFactory::createFormByPage() instead.
*/
public function createPageForm(Page $page, string $name, array $form): ?FormInterface
{
return new Form($page, $name, $form);
}
/**
* Create form using the header of the page.
*
* @param PageInterface $page
* @param string $name
* @param array $form
* @return Form|null
*/
public function createFormForPage(PageInterface $page, string $name, array $form): ?FormInterface
{
return new Form($page, $name, $form);
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace Grav\Plugin\Form;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Page;
use Grav\Framework\Form\Interfaces\FormFactoryInterface;
use Grav\Framework\Form\Interfaces\FormInterface;
class Forms
{
/** @var array|FormFactoryInterface[] */
private $types;
/** @var FormInterface|null */
private $form;
public function __construct()
{
$this->registerType('form', new FormFactory());
}
public function registerType(string $type, FormFactoryInterface $factory): void
{
$this->types[$type] = $factory;
}
public function unregisterType($type): void
{
unset($this->types[$type]);
}
public function hasType(string $type): bool
{
return isset($this->types[$type]);
}
public function getTypes(): array
{
return array_keys($this->types);
}
public function createPageForm(PageInterface $page, string $name = null, array $form = null): ?FormInterface
{
if (null === $form) {
[$name, $form] = $this->getPageParameters($page, $name);
}
if (null === $form) {
return null;
}
$type = $form['type'] ?? 'form';
$factory = $this->types[$type] ?? null;
if ($factory) {
if (method_exists($factory, 'createFormForPage')) {
return $factory->createFormForPage($page, $name, $form);
}
if ($page instanceof Page) {
return $factory->createPageForm($page, $name, $form);
}
}
return null;
}
public function getActiveForm(): ?FormInterface
{
return $this->form;
}
public function setActiveForm(FormInterface $form): void
{
$this->form = $form;
}
protected function getPageParameters(PageInterface $page, ?string $name): array
{
$forms = $page->forms();
if ($name) {
// If form with given name was found, use that.
$form = $forms[$name] ?? null;
} else {
// Otherwise pick up the first form.
$form = reset($forms) ?: null;
$name = (string)key($forms);
}
return [$name, $form];
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "getgrav/grav-plugin-form",
"type": "grav-plugin",
"description": "Grav Plugin Form",
"keywords": ["form"],
"homepage": "https://github.com/getgrav/grav-plugin-form",
"license": "MIT",
"authors": [
{
"name": "Team Grav",
"email": "devs@getgrav.org",
"homepage": "http://getgrav.org",
"role": "Developer"
}
],
"support": {
"issues": "https://github.com/getgrav/grav-plugin-form/issues",
"irc": "https://chat.getgrav.org",
"forum": "https://discourse.getgrav.org",
"docs": "https://github.com/getgrav/grav-plugin-form/blob/master/README.md"
},
"require": {
"php": ">=7.1.3",
"ext-json": "*",
"google/recaptcha": "^1.2"
},
"autoload": {
"psr-4": {
"Grav\\Plugin\\Form\\": "classes/"
},
"classmap": [
"form.php"
]
},
"config": {
"platform": {
"php": "7.1.3"
}
}
}
+71
View File
@@ -0,0 +1,71 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "81d3cf2a625490c40f3f023c7810fb0e",
"packages": [
{
"name": "google/recaptcha",
"version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/google/recaptcha.git",
"reference": "98c4a6573b27e8b0990ea8789c74ea378795134c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/google/recaptcha/zipball/98c4a6573b27e8b0990ea8789c74ea378795134c",
"reference": "98c4a6573b27e8b0990ea8789c74ea378795134c",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.2.20|^2.15",
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^4.8.36|^5.7.27|^6.59|^7.5.11"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"psr-4": {
"ReCaptcha\\": "src/ReCaptcha"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.",
"homepage": "https://www.google.com/recaptcha/",
"keywords": [
"Abuse",
"captcha",
"recaptcha",
"spam"
],
"time": "2019-08-16T15:48:25+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.1.3",
"ext-json": "*"
},
"platform-dev": [],
"platform-overrides": {
"php": "7.1.3"
}
}
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
enabled: true
built_in_css: true
inline_css: true
refresh_prevention: false
client_side_validation: true
inline_errors: false
files:
multiple: false # To allow multiple files, default is single
limit: 10 # Number of allowed files per field (multiple required)
destination: 'self@' # Where to upload the files (path and self@, page@, theme@)
avoid_overwriting: false # Prevent files with the same name to be overridden. Date prefix will be added
random_name: false # Generate a random 15 long string name for the uploaded files
filesize: 0 # Maximum file size allowed (in MB)
accept: # List of mime/types or file extensions allowed (ie, image/*,.zip,.mp4)
- image/*
recaptcha:
version: 2-checkbox
theme: light
site_key:
secret_key:
+84
View File
@@ -0,0 +1,84 @@
'use strict';
var gulp = require('gulp'),
util = require('util'),
path = require('path'),
immutable = require('immutable'),
gulpWebpack = require('gulp-webpack'),
webpack = require('webpack'),
sourcemaps = require('gulp-sourcemaps'),
exec = require('child_process').execSync,
sass = require('gulp-sass'),
cleancss = require('gulp-clean-css'),
csscomb = require('gulp-csscomb'),
rename = require('gulp-rename'),
autoprefixer = require('gulp-autoprefixer'),
pwd = exec('pwd').toString();
// configure the paths
var watch_dir = './scss/**/*.scss';
var src_dir = './scss/*.scss';
var dest_dir = './assets';
var paths = {
source: src_dir
};
var plugins = {},
base = immutable.fromJS(require('./webpack.conf.js')),
options = {
prod: base.mergeDeep({
devtool: 'source-map',
optimization: {minimize: true},
plugins: [
new webpack.DefinePlugin({
'process.env': { NODE_ENV: '"production"' }
}),
new webpack.ProvidePlugin(plugins)
],
output: {
filename: 'form.min.js'
}
})
};
// var compileJS = function(watch) {
// var prodOpts = options.prod.set('watch', watch);
//
// return gulp.src('app/main.js')
// .pipe(gulpWebpack(prodOpts.toJS()))
// .pipe(gulp.dest('assets/'));
// };
var compileCSS = function() {
return gulp.src(paths.source)
.pipe(sourcemaps.init())
.pipe(sass({outputStyle: 'compact', precision: 10})
.on('error', sass.logError)
)
.pipe(sourcemaps.write())
.pipe(autoprefixer())
.pipe(gulp.dest(dest_dir))
.pipe(csscomb())
.pipe(cleancss())
.pipe(rename({
suffix: '.min'
}))
.pipe(gulp.dest(dest_dir));
}
// gulp.task('js', function() {
// compileJS(false);
// });
gulp.task('css', function() {
compileCSS();
});
gulp.task('watch', function() {
gulp.watch(watch_dir, ['css']);
// compileJS(true);
});
gulp.task('all', ['css']);
gulp.task('default', ['all']);
+15
View File
@@ -0,0 +1,15 @@
{
"project":"grav-plugin-form",
"platforms":{
"grav":{
"nodes":{
"plugin":[
{
"source":"/",
"destination":"/user/plugins/form"
}
]
}
}
}
}
+760
View File
@@ -0,0 +1,760 @@
en:
PLUGIN_FORM:
NOT_VALIDATED: "Form not validated. One or more required fields are missing."
NONCE_NOT_VALIDATED: "Oops there was a problem, please check your input and submit the form again."
FILES: "Files Upload"
FORM_ALREADY_SUBMITTED: "This form has already been submitted."
ALLOW_MULTIPLE: "Allow More than one file"
ALLOW_MULTIPLE_HELP: "Allows to select more than one file for upload."
DESTINATION: "Destination"
DESTINATION_HELP: "The location where the files should be uploaded to"
ACCEPT: "Allowed MIME Types"
ACCEPT_HELP: "A list of MIME Types that are allowed for upload"
ERROR_VALIDATING_CAPTCHA: "reCAPTCHA bot protection has identified this form submission is problematic"
DATA_SUMMARY: "Here is the summary of what you wrote to us:"
NO_FORM_DATA: "No form data available"
RECAPTCHA: "reCAPTCHA"
RECAPTCHA_VERSION: "Version"
RECAPTCHA_VERSION_V2_CHECKBOX: "v2 - Checkbox"
RECAPTCHA_VERSION_V2_INVISIBLE: "v2 - Invisible"
RECAPTCHA_VERSION_V3_LATEST: "v3 - Latest"
RECAPTCHA_THEME: "Theme"
RECAPTCHA_THEME_LIGHT: "Light Theme"
RECAPTCHA_THEME_DARK: "Dark Theme"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "For more info visit https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Secret key"
RECAPTCHA_SECRET_KEY_HELP: "For more info visit https://developers.google.com/recaptcha"
GENERAL: "General"
USE_BUILT_IN_CSS: "Use built-in CSS"
USE_INLINE_CSS: "Use inline CSS"
FILEUPLOAD_PREVENT_SELF: 'Cannot use "%s" outside of pages.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Unable to upload file %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Unable to move file %s to "%s"'
DROPZONE_CANCEL_UPLOAD: 'Cancel upload'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Are you sure you want to cancel this upload?'
DROPZONE_DEFAULT_MESSAGE: 'Drop your files here or <strong>click in this area</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Your browser does not support drag and drop file uploads.'
DROPZONE_FALLBACK_TEXT: 'Please use the fallback form below to upload your files like in the olden days.'
DROPZONE_FILE_TOO_BIG: 'File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "You can't upload files of this type."
DROPZONE_MAX_FILES_EXCEEDED: "You can not upload any more files."
DROPZONE_REMOVE_FILE: "Remove file"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Are you sure you want to delete this file?'
DROPZONE_RESPONSE_ERROR: "Server responded with {{statusCode}} code."
YES: "Yes"
NO: "No"
REFRESH_PREVENTION: "Refresh prevention"
REFRESH_PREVENTION_HELP: "Use the form's unique ID to ensure the same form is not reprocessed when refreshing the browser"
LIMIT: "Limit"
LIMIT_HELP: "Maximum number of allowed files per field (multiple required)"
FILESIZE: "Max Filesize"
FILESIZE_HELP: "Maximum file size allowed (in MB), 0 = Use system default"
AVOID_OVERWRITING: "Avoid overwriting"
AVOID_OVERWRITING_HELP: "Prevent files with the same name to be overridden. Date prefix will be added"
RANDOM_NAME: "Random name"
RANDOM_NAME_HELP: "Generate a random 15 long string name for the uploaded files"
CLIENT_SIDE_VALIDATION: "Client-side validation"
CLIENT_SIDE_VALIDATION_HELP: "By default forms will use HTML5 client validation as first line of defense"
INLINE_ERRORS: "Inline errors"
INLINE_ERRORS_HELP: "Useful with 'client-side validation' off, shows inline contextual form errors"
RESOLUTION_MIN: "The {{attr}} was less than the minimum of {{min}}px <br />"
RESOLUTION_MAX: "The {{attr}} was more than the maximum of {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "Destination not specified"
INVALID_MIME_TYPE: "The MIME type %s for the file %s is not accepted."
INVALID_FILE_EXTENSION: "The File Extension for the file %s is not accepted."
eu:
PLUGIN_FORM:
NOT_VALIDATED: "Formularioa ez da baliozkotu. Beharrezkoa den eremu bat edo gehiago falta dira."
NONCE_NOT_VALIDATED: "Arazo bat gertatu da. Egiaztatu sartutako informazioa eta bidali berriro formularioa."
FILES: "Fitxategi Kargatzea"
FORM_ALREADY_SUBMITTED: "Formulario hau dagoeneko bidali da."
ALLOW_MULTIPLE: "Onartu fitxategi bat baino gehiago"
ALLOW_MULTIPLE_HELP: "Fitxategi bat baino gehiago kargatzeko aukera ematen du."
DESTINATION: "Helmuga"
DESTINATION_HELP: "Fitxategiak non kokatu behar diren"
ACCEPT: "Baimendutako MIME motak"
ACCEPT_HELP: "Kargatu ahal diren MIME moten zerrenda"
ERROR_VALIDATING_CAPTCHA: "Errorea reCAPTCHA egiaztatzerakoan"
DATA_SUMMARY: "Hona hemen idatzi diguzunaren laburpena:"
NO_FORM_DATA: "Formularioaren datuak ez daude eskuragarri"
RECAPTCHA: "reCAPTCHA"
RECAPTCHA_VERSION: "Bertsioa"
RECAPTCHA_VERSION_V2_CHECKBOX: "v2 - Checkbox"
RECAPTCHA_VERSION_V2_INVISIBLE: "v2 - Invisible"
RECAPTCHA_VERSION_V3_LATEST: "v3 - Latest"
RECAPTCHA_THEME: "Itxura"
RECAPTCHA_THEME_LIGHT: "Itxura Argia"
RECAPTCHA_THEME_DARK: "Itxura Iluna"
RECAPTCHA_SITE_KEY: "Gunearen gakoa"
RECAPTCHA_SITE_KEY_HELP: "Gehiago jakiteko bisitatu https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Gako Sekretua"
RECAPTCHA_SECRET_KEY_HELP: "Gehiago jakiteko bisitatu https://developers.google.com/recaptcha"
GENERAL: "Orokorra"
USE_BUILT_IN_CSS: "Erabili CSS integratua"
USE_INLINE_CSS: "Erabili CSS lerroan"
FILEUPLOAD_PREVENT_SELF: 'Ezin da "%s" erabili orrialdeetatik kanpo.'
FILEUPLOAD_UNABLE_TO_UPLOAD: '%s: %s fitxategia ezin da kargatu'
FILEUPLOAD_UNABLE_TO_MOVE: '%s fitxategia ezin da mugitu "%s"-ra'
DROPZONE_CANCEL_UPLOAD: 'Utzi kargatzea'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Ziur al zaude kargatzea bertan behera utzi nahi duzula?'
DROPZONE_DEFAULT_MESSAGE: 'Utzi zure fitxategiak hemen edo <strong> klik egin eremu honetan </strong>'
DROPZONE_FALLBACK_MESSAGE: 'Zure nabigatzaileak ez du jasaten fitxategiak arrastratzea.'
DROPZONE_FALLBACK_TEXT: 'Mesedez erabili beheko formularioa zure fitxategiak kargatzeko.'
DROPZONE_FILE_TOO_BIG: 'Fitxategiak asko pisatzen du ({{filesize}}MiB). Gehienezko tamaina: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Ezin dituzu mota honetako fitxategiak kargatu."
DROPZONE_MAX_FILES_EXCEEDED: "Ezin dituzu fitxategi gehiago kargatu."
DROPZONE_REMOVE_FILE: "Fitxategia ezabatu"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Zihur al zaude fitxategia ezabatu nahi duzula?'
DROPZONE_RESPONSE_ERROR: "Zerbitzariak{{statusCode}} kodearekin erantzun du."
YES: "Bai"
NO: "Ez"
REFRESH_PREVENTION: "Eguneratze prebentzioa"
REFRESH_PREVENTION_HELP: "Erabili formularioaren ID bakarra, zihurtatzeko ez dela berriro prozesatuko nabigatzailea eguneratzerakoan."
LIMIT: "Muga"
LIMIT_HELP: "Baimendutako gehienezko fitxategi kopurua eremu bakoitzeko (beharrezkoak dira fitxategi anitz)"
FILESIZE: "Gehienezko fitxategi tamaina"
FILESIZE_HELP: "Onartutako gehienezko fitxategi tamaina (MB-tan), 0 = Sistemaren lehenetsia"
AVOID_OVERWRITING: "Gainean idaztea ekidin"
AVOID_OVERWRITING_HELP: "Izen bereko fitxategiak gainean idaztea ekidin. Data aurrizkia gehituko da"
RANDOM_NAME: "Ausazko izena"
RANDOM_NAME_HELP: "Sortu ausazko 15 karakteredun kate luze bat, izen bezala, kargatutako fitxategientzat"
CLIENT_SIDE_VALIDATION: "Bezeroaren aldeko baliozkotzea"
CLIENT_SIDE_VALIDATION_HELP: "Lehenetsita, formularioek HTML5 bezeroaren aldeko baliozkotzea erabiliko dute lehenengo defentsa moduan."
INLINE_ERRORS: "Erroreak lerroan"
INLINE_ERRORS_HELP: "Erabilgarria den 'bezeroaren aldeko baliozkotzea' desgaituta dago, testuinguruko formulario erroreak erakusten ditu"
RESOLUTION_MIN: "{{Attr}} atributua {{min}}px gutxienekoa baino txikiagoa izan zen <br />"
RESOLUTION_MAX: "{{Attr}} atributua {{max}}px gehienekoa baino handiagoa izan da <br />"
DESTINATION_NOT_SPECIFIED: "Helmuga ez da zehaztu"
INVALID_MIME_TYPE: "%s fitxategiaren %s MIME mota ez da onartzen."
INVALID_FILE_EXTENSION: "%s fitxategiaren luzapena ez da onartzen."
de:
PLUGIN_FORM:
NOT_VALIDATED: "Formularwerte nicht gültig. Für ein oder mehrere erforderliche Felder fehlen Werte."
NONCE_NOT_VALIDATED: "Ups, es gibt da ein Problem. Eingabewerte bitte noch mal prüfen und das Formular erneut absenden."
FILES: "Dateien hochladen"
FORM_ALREADY_SUBMITTED: "Formular wurde bereits versendet."
ALLOW_MULTIPLE: "Erlaube mehr als eine Datei"
ALLOW_MULTIPLE_HELP: "Erlaubt es, mehr als eine Datei zum Hochladen auszuwählen."
DESTINATION: "Ziel"
DESTINATION_HELP: "Das Ziel, wohin die Dateien hochgeladen werden sollen."
ACCEPT: "Erlaube MIME-Typen"
ACCEPT_HELP: "Eine Liste von MIME-Typen, die hochgeladen werden dürfen."
ERROR_VALIDATING_CAPTCHA: "Die Überprüfung des Captcha ist fehlgeschlagen."
DATA_SUMMARY: "Hier die Zusammenfassung von dem, was Sie geschrieben haben:"
YES: "Ja"
NO: "Nein"
NO_FORM_DATA: "Keine Formulardaten vorhanden"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Seiten Schlüssel"
RECAPTCHA_SITE_KEY_HELP: "Für mehr Informationen besuche https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Privater Schlüssel"
RECAPTCHA_SECRET_KEY_HELP: "Für mehr Informationen besuche https://developers.google.com/recaptcha"
GENERAL: "General"
USE_BUILT_IN_CSS: "Benutze built-in CSS"
USE_INLINE_CSS: "Benutze inline CSS"
FILEUPLOAD_PREVENT_SELF: '"%s" kann nicht ausserhalb der Seiten verwendet werden.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Upload Fehler %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Fehler beim verschieben von %s nach "%s"'
DROPZONE_CANCEL_UPLOAD: 'Upload abgebrochen'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Bist du sicher das Du diesen Upload abbrechen möchtest??'
DROPZONE_DEFAULT_MESSAGE: 'Ziehe deine Dateien hierhin <strong>Klicke hier</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Dein Browser unterstützt kein "drag and drop".'
DROPZONE_FILE_TOO_BIG: 'Datei zu gross ({{filesize}}MiB). Max Dateigrösse: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Du kannst keine Dateien dieses Typs hochladen."
DROPZONE_MAX_FILES_EXCEEDED: "DU kannst keine weiteren Dateien hochladen."
DROPZONE_REMOVE_FILE: "Datei entfernen"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Möchtest du diese Datei wirklich löschen?'
DROPZONE_RESPONSE_ERROR: "Server antwortet mit {{statusCode}}."
LIMIT_HELP: "Maximale anzahl erlaubter Dateien (Mehrfachauswahl erforderlich) "
FILESIZE: "Max Dateigrösse"
FILESIZE_HELP: "Maximale Dateigrösse (in MB), 0 = Benutze Standart"
AVOID_OVERWRITING: "Überschreibungen verhindern"
AVOID_OVERWRITING_HELP: "Verhindert überschreibungen und fügt ein Prefix an."
RANDOM_NAME: "Zufälliger Name"
RANDOM_NAME_HELP: "Generiert einen 15 Zeichen langen Zufalls Name."
CLIENT_SIDE_VALIDATION: "Klientseitige Überprüfung"
INLINE_ERRORS: "Inline errors"
DESTINATION_NOT_SPECIFIED: "Zielort nicht definiert"
INVALID_MIME_TYPE: "Der MIME-Type %s für die Datei %s ist nicht erlaubt."
INVALID_FILE_EXTENSION: "Die Dateiendung %s ist nicht erlaubt."
es:
PLUGIN_FORM:
NOT_VALIDATED: "Falló la validación del formulario. Uno o más campos obligatorios no fueron cubiertos."
NONCE_NOT_VALIDATED: "Oops, hay un problema, por favor revise la información e intente enviar el formulario otra vez."
FILES: "Subida de Ficheros"
FORM_ALREADY_SUBMITTED: "Este formulario ya ha sido enviado"
ALLOW_MULTIPLE: "Permitir más de un fichero"
ALLOW_MULTIPLE_HELP: "Permitir seleccionar más de un fichero para subir."
DESTINATION: "Destino"
DESTINATION_HELP: "El lugar de destino al que subir los ficheros"
ACCEPT: "MIME Types permitidos"
ACCEPT_HELP: "Una lista de MIME Types que se permiten subir."
ERROR_VALIDATING_CAPTCHA: "Error al comprobar el Captcha"
DATA_SUMMARY: "Este es el resumen de lo que nos has escrito:"
NO_FORM_DATA: "No hay datos disponibles del formulario"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "Para más información visita https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Secret key"
RECAPTCHA_SECRET_KEY_HELP: "Para más información visita https://developers.google.com/recaptcha"
GENERAL: "General"
USE_BUILT_IN_CSS: "Utilizar CSS incorporado"
USE_INLINE_CSS: "Utilizar CSS en línea"
FILEUPLOAD_PREVENT_SELF: 'No se puede utilizar "%s" fuera de las páginas'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'No se puede subir el fichero %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'No se puede mover el fichero %s to "%s"'
DROPZONE_CANCEL_UPLOAD: 'Cancelar subida'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: '¿Estas seguro que quieres cancelar esta subida?'
DROPZONE_DEFAULT_MESSAGE: 'Arrastra tus ficheros aquí o <strong>haz click en esta área</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Tu navegador no soporta el arrastrar y soltar ficheros de subida.'
DROPZONE_FALLBACK_TEXT: 'Por favor utiliza el formulario alternativo que esta debajo, para subir ficheros como antiguamente.'
DROPZONE_FILE_TOO_BIG: 'Fichero demasiado grande ({{filesize}}MiB). Máximo tamaño: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "No puedes subir ficheros de este tipo."
DROPZONE_MAX_FILES_EXCEEDED: "No puedes subir mas ficheros."
DROPZONE_REMOVE_FILE: "Borrar fichero"
DROPZONE_REMOVE_FILE_CONFIRMATION: '¿Estas seguro de que quieres borrar este fichero?'
DROPZONE_RESPONSE_ERROR: "El servidor respondió con el código de estado {{statusCode}}."
YES: "Si"
NO: "No"
REFRESH_PREVENTION: "Prevenir refresco"
REFRESH_PREVENTION_HELP: "Utiliza el ID único del formulario para garantizar que el este no se vuelve a procesar cuando se refresca el navegador"
LIMIT: "Límite"
LIMIT_HELP: "Máximo número de ficheros permitidos por campo (múltiples requeridos)"
FILESIZE: "Tamaño máximo de fichero"
FILESIZE_HELP: "Tamaño máximo de fichero permitido (en MB), 0 = Utiliza el de por defecto del sistema"
AVOID_OVERWRITING: "Evitar sobreescribir"
AVOID_OVERWRITING_HELP: "Evita que los ficheros con el mismo nombre sean sobreescritos. Se agregará el prefijo de fecha"
RANDOM_NAME: "Nombre aleatorio"
RANDOM_NAME_HELP: "Genera una cadela aleatoria de 15 caracteres como nombre de fichero a subir"
CLIENT_SIDE_VALIDATION: "Validación del lado del cliente"
CLIENT_SIDE_VALIDATION_HELP: "Por defecto los formularios usarán validación HTML5 del lado del cliente como primera línea de defensa"
INLINE_ERRORS: "Errores en línea"
INLINE_ERRORS_HELP: "Útil con 'validación del lado del cliente' desactivado, muestra errores en el formulario contextual en línea"
RESOLUTION_MIN: "{{attr}} fué menor que el mínimo {{min}}px <br />"
RESOLUTION_MAX: "{{attr}} fué mayor que el máximo {{max}}px <br />"
fr:
PLUGIN_FORM:
NOT_VALIDATED: "Formulaire non validé. Un ou plusieurs champs obligatoires sont manquants."
NONCE_NOT_VALIDATED: "Oups, un problème est survenu. Veuillez vérifier votre saisie et soumettre à nouveau le formulaire."
FILES: "Fichiers chargés"
ALLOW_MULTIPLE: "Autoriser plus d'un fichier"
ALLOW_MULTIPLE_HELP: "Permet la sélection de plusieurs fichiers pour chargement."
DESTINATION: "Destination"
DESTINATION_HELP: "L'emplacement où les fichiers doivent être chargés."
ACCEPT: "Autoriser les Types MIME"
ACCEPT_HELP: "Liste des Types MIME autorisés au chargement"
ERROR_VALIDATING_CAPTCHA: "Erreur lors de la validation du Captcha"
DATA_SUMMARY: "Voici le résumé de ce que vous nous avez écrit :"
NO_FORM_DATA: "Aucune donnée de formulaire disponible"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Clé du site"
RECAPTCHA_SITE_KEY_HELP: "Pour plus d'informations veuillez vous rendre sur https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Clé secrète"
RECAPTCHA_SECRET_KEY_HELP: "Pour plus d'informations veuillez vous rendre sur https://developers.google.com/recaptcha"
GENERAL: "Général"
USE_BUILT_IN_CSS: "Utiliser les CSS natifs"
FILEUPLOAD_PREVENT_SELF: "Impossible d'utiliser '%s' en dehors des pages."
FILEUPLOAD_UNABLE_TO_UPLOAD: "Impossible de charger le fichier %s: %s"
FILEUPLOAD_UNABLE_TO_MOVE: 'Impossible de déplacer le fichier %s vers "%s"'
DROPZONE_CANCEL_UPLOAD: "Annuler le chargement"
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: "Êtes-vous certain de vouloir annuler ce téléchargement ?"
DROPZONE_DEFAULT_MESSAGE: "Glissez vos fichiers ici ou <strong>cliquez dans cette zone</strong>"
DROPZONE_FALLBACK_MESSAGE: "Votre navigateur ne prend pas en charge les téléchargements par glissé-déposé."
DROPZONE_FALLBACK_TEXT: "Veuillez utiliser le formulaire de secours ci-dessous pour transférer vos fichiers."
DROPZONE_FILE_TOO_BIG: "Le fichier est trop volumineux ({{filesize}}MiB). Taille maximale de fichier : {{maxFilesize}}MiB."
DROPZONE_INVALID_FILE_TYPE: "Vous ne pouvez pas charger des fichiers de ce type."
DROPZONE_MAX_FILES_EXCEEDED: "Vous ne pouvez plus télécharger de fichiers."
DROPZONE_REMOVE_FILE: "Supprimer le fichier"
DROPZONE_REMOVE_FILE_CONFIRMATION: "Êtes-vous sûr de vouloir supprimer ce fichier ?"
DROPZONE_RESPONSE_ERROR: "Le serveur a répondu avec le code {{statusCode}}."
YES: "Oui"
NO: "Non"
REFRESH_PREVENTION: "Prévenir la redondance"
REFRESH_PREVENTION_HELP: "Utilise lidentifiant unique du formulaire pour sassurer quil nest pas pris deux fois en compte si lon recharge le navigateur."
LIMIT: "Limite"
LIMIT_HELP: "Nombre maximum de fichiers par champ (quand multiple)"
FILESIZE: "Poids du fichier max."
FILESIZE_HELP: "Poids maximum autorisé pour le fichier (en MB), 0 = utiliser la valeur du système par défaut"
AVOID_OVERWRITING: "Éviter la réécriture"
AVOID_OVERWRITING_HELP: "Évite quun fichier avec le même nom soit écrasé. Le nom sera préfixé de la date."
RANDOM_NAME: "Nom aléatoire"
RANDOM_NAME_HELP: "Génère une chaîne de caractère aléatoire de quinze caractères pour le nom du fichier uploadé."
CLIENT_SIDE_VALIDATION: "Validation côté client"
CLIENT_SIDE_VALIDATION_HELP: "Par défaut, les formulaires utiliseront la validation en HTML5 avant tout autre validation."
INLINE_ERRORS: "Erreurs en ligne"
INLINE_ERRORS_HELP: "Utile avec « Validation côté client » désactivé ; affiche des erreurs contextuelles en ligne."
RESOLUTION_MIN: "{{attr}} est inférieur au minimum de {{min}}px <br />"
RESOLUTION_MAX: "{{attr}} est supérieur au minimum de {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "La destination nest pas indiquée."
INVALID_MIME_TYPE: "Le type MIME %s pour le fichier %s nest pas accepté."
INVALID_FILE_EXTENSION: "Lextension pour le fichier %s nest pas acceptée."
ru:
PLUGIN_FORM:
NOT_VALIDATED: "Форма не подтверждена. Отсутствует одно или несколько обязательных полей."
NONCE_NOT_VALIDATED: "Упс, у вас возникла проблема, проверьте свои данные и отправьте форму еще раз."
FILES: "Загрузка файлов"
FORM_ALREADY_SUBMITTED: "Эта форма уже была отправлена."
ALLOW_MULTIPLE: "Разрешить несколько файлов"
ALLOW_MULTIPLE_HELP: "Позволяет выбрать более одного файла для загрузки."
DESTINATION: "Место назначения"
DESTINATION_HELP: "Место, куда файлы должны быть загружены в"
ACCEPT: "Разрешенные MIME типы"
ACCEPT_HELP: "Список MIME типов, разрешенных для загрузки"
ERROR_VALIDATING_CAPTCHA: "Ошибка проверки Captcha"
DATA_SUMMARY: "Вот краткое изложение того, что вы нам написали:"
NO_FORM_DATA: "Данные формы отсутствуют"
RECAPTCHA: "reCAPTCHA"
RECAPTCHA_VERSION: "Версия"
RECAPTCHA_VERSION_V2_CHECKBOX: "v2 - Флажок"
RECAPTCHA_VERSION_V2_INVISIBLE: "v2 - Невидимый"
RECAPTCHA_VERSION_V3_LATEST: "v3 - Последний"
RECAPTCHA_THEME: "Тема"
RECAPTCHA_THEME_LIGHT: "Светлая тема"
RECAPTCHA_THEME_DARK: "Темная тема"
RECAPTCHA_SITE_KEY: "Ключ сайта (site key)"
RECAPTCHA_SITE_KEY_HELP: "Для получения дополнительной информации посетите https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Секретный ключ (secret key)"
RECAPTCHA_SECRET_KEY_HELP: "Для получения дополнительной информации посетите https://developers.google.com/recaptcha"
GENERAL: "Общие"
USE_BUILT_IN_CSS: "Использовать встроенный CSS"
USE_INLINE_CSS: "Использовать inline CSS"
FILEUPLOAD_PREVENT_SELF: 'Нельзя использовать "%s" за пределами страниц.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Не удалось загрузить файл %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Не удалось переместить файл %s в "%s"'
DROPZONE_CANCEL_UPLOAD: 'Отменить загрузку'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Вы действительно хотите отменить эту загрузку?'
DROPZONE_DEFAULT_MESSAGE: 'Перетащите свои файлы сюда или <strong>щелкните в этой области</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Ваш браузер не поддерживает загрузку файлов с перетаскиванием.'
DROPZONE_FALLBACK_TEXT: 'Пожалуйста, используйте приведенную ниже форму для загрузки ваших файлов, как в старые времена.'
DROPZONE_FILE_TOO_BIG: 'Файл слишком большой ({{filesize}}мб). Максимальный размер файла: {{maxFilesize}}мб.'
DROPZONE_INVALID_FILE_TYPE: "Вы не можете загружать файлы этого типа."
DROPZONE_MAX_FILES_EXCEEDED: "Вы не можете загружать больше файлов."
DROPZONE_REMOVE_FILE: "Удалить файл"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Вы действительно хотите удалить этот файл?'
DROPZONE_RESPONSE_ERROR: "Сервер ответил кодом {{statusCode}}."
YES: "Да"
NO: "Нет"
REFRESH_PREVENTION: "Предотвращение обновления"
REFRESH_PREVENTION_HELP: "Используйте уникальный идентификатор формы, чтобы гарантировать, что одна и та же форма не будет отправляться заново при обновлении страницы браузера"
LIMIT: "Лимит"
LIMIT_HELP: "Максимальное количество разрешенных файлов в поле (требуется несколько)"
FILESIZE: "Максимальный размер файла"
FILESIZE_HELP: "Максимально допустимый размер файла (в МБ), 0 = Использовать системные установки по умолчанию"
AVOID_OVERWRITING: "Избегать перезаписи"
AVOID_OVERWRITING_HELP: "Не допускать перезаписи файлов с тем же именем. Будет добавлен префикс с датой"
RANDOM_NAME: "Случайное имя"
RANDOM_NAME_HELP: "Создавать случайное строковое имя из 15 символов для загруженных файлов"
CLIENT_SIDE_VALIDATION: "Проверка на стороне клиента"
CLIENT_SIDE_VALIDATION_HELP: "По умолчанию формы будут использовать HTML5 проверку клиента как первую степень защиты"
INLINE_ERRORS: "Встроенные ошибки"
INLINE_ERRORS_HELP: "Полезно с отключенной «проверкой на стороне клиента», отображает встроенные контекстные ошибки формы"
RESOLUTION_MIN: "{{attr}} меньше минимума {{min}}px <br />"
RESOLUTION_MAX: "The {{attr}} больше, чем максимум {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "Назначение не указано"
INVALID_MIME_TYPE: "Тип MIME %s для файла %s не принимается."
INVALID_FILE_EXTENSION: "Расширение файла %s не является допустимым для файла."
uk:
PLUGIN_FORM:
NOT_VALIDATED: "Форма не підтверджена. Відсутнє одне або кілька обов'язкових полів."
NONCE_NOT_VALIDATED: "Упс, у вас виникла проблема, перевірте свої дані і відправте форму ще раз."
FILES: "Завантаження файлів"
FORM_ALREADY_SUBMITTED: "Ця форма вже була відправлена."
ALLOW_MULTIPLE: "Дозволити декілька файлів"
ALLOW_MULTIPLE_HELP: "Дозволяє вибрати більше одного файлу для завантаження."
DESTINATION: "Місце призначення"
DESTINATION_HELP: "Місце, куди файли повинні бути завантажені в"
ACCEPT: "Дозволені MIME типи"
ACCEPT_HELP: "Перелік MIME типів, дозволених для завантаження"
ERROR_VALIDATING_CAPTCHA: "Помилка перевірки Captcha"
DATA_SUMMARY: "Ось стислий виклад того, що ви нам написали:"
NO_FORM_DATA: "Дані форми відсутні"
RECAPTCHA: "reCAPTCHA"
RECAPTCHA_VERSION: "Версія"
RECAPTCHA_SITE_KEY: "Ключ сайту (site key)"
RECAPTCHA_SITE_KEY_HELP: "Для отримання додаткової інформації відвідайте https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Секретний ключ (secret key)"
RECAPTCHA_SECRET_KEY_HELP: "Для отримання додаткової інформації відвідайте https://developers.google.com/recaptcha"
GENERAL: "Загальні"
USE_BUILT_IN_CSS: "Використовувати вбудований CSS"
USE_INLINE_CSS: "Використовувати inline CSS"
FILEUPLOAD_PREVENT_SELF: 'Не можна використовувати "%s" за межами сторінок.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Не вдалося завантажити файл %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Не вдалося перемістити файл %s до "%s"'
DROPZONE_CANCEL_UPLOAD: 'Скасувати завантаження'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Ви дійсно хочете скасувати це завантаження?'
DROPZONE_DEFAULT_MESSAGE: 'Перетягніть свої файли сюди чи <strong>клацніть в цій області</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Ваш браузер не підтримує завантаження файлів з перетягуванням.'
DROPZONE_FALLBACK_TEXT: 'Будь ласка, використовуйте цю форму для завантаження ваших файлів, як в старі часи.'
DROPZONE_FILE_TOO_BIG: 'Файл занадто великий ({{filesize}}мб). Максимальний розмір файлу: {{maxFilesize}}мб.'
DROPZONE_INVALID_FILE_TYPE: "Ви не можете завантажувати файли цього типу."
DROPZONE_MAX_FILES_EXCEEDED: "Ви не можете завантажувати більше файлів."
DROPZONE_REMOVE_FILE: "Видалити файл"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Ви дійсно хочете видалити цей файл?'
DROPZONE_RESPONSE_ERROR: "Сервер відповів кодом {{statusCode}}."
YES: "Так"
NO: "Ні"
REFRESH_PREVENTION: "Запобігання оновлення"
REFRESH_PREVENTION_HELP: "Використовуйте унікальний ідентифікатор форми, щоб гарантувати, що одна і та ж форма не буде відправлятися заново при оновленні сторінки браузера"
LIMIT: "Ліміт"
LIMIT_HELP: "Максимальна кількість дозволених файлів в поле (потрібно декілька)"
FILESIZE: "Максимальний розмір файлу"
FILESIZE_HELP: "Максимально допустимий розмір файлу (в МБ), 0 = Використовувати системні налаштування за замовчуванням"
AVOID_OVERWRITING: "Уникати перезапису"
AVOID_OVERWRITING_HELP: "Не допускати перезапису файлів з тим же ім'ям. Буде додано префікс з датою"
RANDOM_NAME: "Випадкове ім'я"
RANDOM_NAME_HELP: "Створювати випадкове строкове ім'я з 15 символів для завантажених файлів"
CLIENT_SIDE_VALIDATION: "Перевірка на стороні клієнта"
CLIENT_SIDE_VALIDATION_HELP: "За замовчуванням форми будуть використовувати HTML5 перевірку клієнта як першу ступінь захисту"
INLINE_ERRORS: "Вбудовані помилки"
INLINE_ERRORS_HELP: "Корисно з відключеною «перевіркою на стороні клієнта», відображає вбудовані контекстні помилки форми"
RESOLUTION_MIN: "{{attr}} менше, ніж мінімум {{min}}px <br />"
RESOLUTION_MAX: "The {{attr}} більше, ніж максимум {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "Призначення не вказано"
INVALID_MIME_TYPE: "Тип MIME %s для файла %s не приймається."
INVALID_FILE_EXTENSION: "Розширення файлу %s не є допустимим для файлу."
hr:
PLUGIN_FORM:
NOT_VALIDATED: "Formular nije validiran. Jedan ili više traženih polja nedostaju."
NONCE_NOT_VALIDATED: "Ups, došlo je do problema, molimo provjerite svoj unos i pokušajte opet."
FILES: "Upload Fajlova"
ALLOW_MULTIPLE: "Dopusti više od jednog fajla"
DESTINATION: "Destinacija"
DESTINATION_HELP: "Lokacija gdje bi fajlovi trebali biti uploadani"
ACCEPT: "Dopušteni MIME Tipovi"
ACCEPT_HELP: "Lista dopuštenih MIME Tipova koji su dozvoljeni za upload"
ERROR_VALIDATING_CAPTCHA: "Greška pri validiranju Captcha"
hu:
PLUGIN_FORM:
NOT_VALIDATED: "Érvénytelen az űrlap. Egy vagy több kötelező mező nincs kitöltve."
NONCE_NOT_VALIDATED: "Upsz, van egy kis probléma, kérlek nézd át az űrlapot, majd küldd el újra."
FILES: "Fájlok Feltöltése"
ALLOW_MULTIPLE: "Több fájl feltöltése"
ALLOW_MULTIPLE_HELP: "Engedélyezi egyszerre több állomány feltöltését."
DESTINATION: "Feltöltés Helye"
DESTINATION_HELP: "Ide lesznek feltöltve az állományok"
ACCEPT: "Engedélyezett MIME-típusok"
ACCEPT_HELP: "A feltölthető állományok MIME-típusainak listája"
ERROR_VALIDATING_CAPTCHA: "Hiba lépett fel a Captcha validálása során"
it:
PLUGIN_FORM:
NOT_VALIDATED: "Il Form risulta invalido. Uno o più campi risultano omessi."
NONCE_NOT_VALIDATED: "Oops è stato riscontrato un errore, si prega di ricontrollare i dati inseriti e provare di nuovo."
FILES: "Invio dei Files"
ALLOW_MULTIPLE: "Consenti più di un file"
ALLOW_MULTIPLE_HELP: "Permette la selezione di più di un file per l'upload"
DESTINATION: "Destinazione"
DESTINATION_HELP: "La destinazione dove i files vengono uploadati"
ACCEPT: "Tipi di MIME Concessi"
ACCEPT_HELP: "Una lista di tipi di MIME che sono permessi per l'upload"
ERROR_VALIDATING_CAPTCHA: "Errore durante la validazione del Captcha"
DATA_SUMMARY: "Ecco il riassunto di ciò che ci hai scritto:"
NO_FORM_DATA: "Nessuna informazione disponibile"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "Per maggiori informazioni visita https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Chiave segreta"
RECAPTCHA_SECRET_KEY_HELP: "Per maggiori informazioni visita https://developers.google.com/recaptcha"
GENERAL: "Generale"
USE_BUILT_IN_CSS: "Usa CSS incorporato"
FILEUPLOAD_PREVENT_SELF: 'Non si può usare "%s" fuori dalle pagine.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Impossibile caricare il file %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Impossibile muovere il file %s to "%s"'
DROPZONE_CANCEL_UPLOAD: 'Trasferimento annullato'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Sei sicuro di voler cancellare questo trasferimento?'
DROPZONE_DEFAULT_MESSAGE: "Trascina qui i tuoi file o <strong>clicca su quest'area</strong>"
DROPZONE_FALLBACK_MESSAGE: 'Il tuo browser non supporta il trascinamento dei file per il trasferimento.'
DROPZONE_FALLBACK_TEXT: 'Utilizza il modulo di riserva qui sotto per caricare i tuoi file come ai vecchi tempi.'
DROPZONE_FILE_TOO_BIG: 'Il file è troppo grande ({{filesize}}MiB). Dimensione massima consentita: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Non puoi caricare questo tipo di file"
DROPZONE_MAX_FILES_EXCEEDED: "Non puoi caricare ulteriori file, hai raggiunto il limite consentito."
DROPZONE_REMOVE_FILE: "Rimuovi il file"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Sei sicuro di voler eliminare questo file??'
DROPZONE_RESPONSE_ERROR: "Il Server ha risposto con il codice {{statusCode}}."
YES: "Si"
NO: "No"
ro:
PLUGIN_FORM:
NOT_VALIDATED: "Formularul nu a fost validat. Unul sau mai multe câmpuri sunt goale."
NONCE_NOT_VALIDATED: "Oops a apărut o problemă, vă rugăm verificați datele introduse și trimiteți formularul din nou."
FILES: "Încărcare fișiere"
ALLOW_MULTIPLE: "Permiteți mai multe fișiere"
ALLOW_MULTIPLE_HELP: "Vă permite să selectați mai multe fișiere pentru încărcare."
DESTINATION: "Destinație"
DESTINATION_HELP: "Locația unde vor fi încărcate fișierele."
ACCEPT: "Permite tipuri MIME "
ACCEPT_HELP: "O listă cu tipuri MIME care sunt permise la încărcare."
ERROR_VALIDATING_CAPTCHA: "Eroare la validarea Captcha."
DATA_SUMMARY: "Mai jos aveți un rezumat al mesajului pe care ni l-ați trimis:"
NO_FORM_DATA: "Nu e disponibilă nici o dată pentru formular"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Cheia pentru Site"
RECAPTCHA_SITE_KEY_HELP: "Pentru mai multe detalii vă rugăm vizitați https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Cheia secretă pentru Site"
RECAPTCHA_SECRET_KEY_HELP: "Pentru mai multe detalii vă rugăm vizitați https://developers.google.com/recaptcha"
cs:
PLUGIN_FORM:
NOT_VALIDATED: "Formulář nebyl ověřen. Chybí jedno nebo více povinných polí."
NONCE_NOT_VALIDATED: "Jejda, došlo k problému, zkontrolujte vstupní stránku a znovu odešlete formulář."
FILES: "Nahrávání souborů"
ALLOW_MULTIPLE: "Povolit více než jeden soubor"
ALLOW_MULTIPLE_HELP: "Umožňuje vybrat více než jeden soubor pro nahrání."
DESTINATION: "Cílové umístění"
DESTINATION_HELP: "Místo, kam mají být soubory nahrány"
ACCEPT: "Povolené MIME typy"
ACCEPT_HELP: "Seznam MIME typů souborů povolených pro upload"
ERROR_VALIDATING_CAPTCHA: "Nepodařilo se ověřit CAPTCHA (kontrola proti spamu)"
DATA_SUMMARY: "Shrnutí toho, co jste nám napsali:"
NO_FORM_DATA: "Formulář neobsahuje žádná data"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "Více informací https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Secret key"
RECAPTCHA_SECRET_KEY_HELP: "Více informací https://developers.google.com/recaptcha"
GENERAL: "Všeobecné"
USE_BUILT_IN_CSS: "Použít built-in CSS"
FILEUPLOAD_PREVENT_SELF: 'Nelze použít "% s" mimo stránky.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Nelze nahrát soubor %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Nelze přesunout soubor %s do "%s"'
DROPZONE_CANCEL_UPLOAD: 'Zrušit upload'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Opravdu chcete zrušit nahrávání souboru?'
DROPZONE_DEFAULT_MESSAGE: 'Přetáhněte sem soubory nebo <strong>klikněte v tomto prostoru</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Váš prohlížeč nepodporuje nahrávání souborů táhni a pusť.'
DROPZONE_FALLBACK_TEXT: 'Použijte níže uvedený formulář pro nahrání souborů, jako v minulých dnech.'
DROPZONE_FILE_TOO_BIG: 'Soubor je příliš velký ({{filesize}}MiB). Max. velikost souboru: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Nelze nahrát soubory tohoto typu."
DROPZONE_MAX_FILES_EXCEEDED: "Nelze nahrát další soubory."
DROPZONE_REMOVE_FILE: "Odstranit soubor"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Opravdu chcete tento soubor smazat?'
DROPZONE_RESPONSE_ERROR: "Server vrátil chybový kód: {{statusCode}}."
YES: "Ano"
NO: "Ne"
pl:
PLUGIN_FORM:
NOT_VALIDATED: "Formularz został odrzucony. Jedno lub więcej pól wymaga wypełnienia."
NONCE_NOT_VALIDATED: "Ups, pojawił się problem. Proszę sprawdzić, czy wszystkie pola zostały wypełnione i wysłać formularz ponownie."
FILES: "Plik przesłany."
FORM_ALREADY_SUBMITTED: "Ten formularz został już wysłany."
ALLOW_MULTIPLE: "Zezwól na więcej niż jeden plik."
ALLOW_MULTIPLE_HELP: "Zezwól na przesłanie więcej niż jednego pliku."
DESTINATION: "Cel"
DESTINATION_HELP: "Lokalizacja przesłanego pliku."
ACCEPT: "Dozwolone typy MIME"
ACCEPT_HELP: "Lista dozwolonych do przesłania typów MIME"
ERROR_VALIDATING_CAPTCHA: "Błąd walidacji Captcha"
DATA_SUMMARY: "Oto podsumowanie informacji, które do nas napisałeś:"
NO_FORM_DATA: "Brak dostępnych danych formularza"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Klucz strony"
RECAPTCHA_SITE_KEY_HELP: "Aby uzyskać więcej informacji, odwiedź https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Tajny klucz"
RECAPTCHA_SECRET_KEY_HELP: "Aby uzyskać więcej informacji, odwiedź https://developers.google.com/recaptcha"
GENERAL: "Ogólne"
USE_BUILT_IN_CSS: "Użyj wbudowanych stylów CSS"
USE_INLINE_CSS: "Użyj stylów inline CSS"
FILEUPLOAD_PREVENT_SELF: 'Nie można użyć "%s" poza stronami.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Nie można przesłać pliku %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Nie można przenieść pliku %s do "%s"'
DROPZONE_CANCEL_UPLOAD: 'Anuluj przesyłanie'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Czy na pewno chcesz anulować to przesyłanie?'
DROPZONE_DEFAULT_MESSAGE: 'Upuść pliki tutaj lub <strong>kliknij w tym obszarze</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Twoja przeglądarka nie wspiera przesyłania plików metodą przeciągnij i upuść.'
DROPZONE_FALLBACK_TEXT: 'Proszę użyć zapasowego formularza do przesyłania plików znajdującego się poniżej.'
DROPZONE_FILE_TOO_BIG: 'Plik jest zbyt duży ({{filesize}}MiB). Maksymalna wielkość pliku: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Nie możesz przesłać pliku w tym formacie."
DROPZONE_MAX_FILES_EXCEEDED: "Nie możesz już przesłać więcej plików."
DROPZONE_REMOVE_FILE: "Usuń plik"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Czy na pewno chcesz usunąć ten plik?'
DROPZONE_RESPONSE_ERROR: "Serwer odpowiedział kodem {{statusCode}}."
YES: "Tak"
NO: "Nie"
REFRESH_PREVENTION: "Zapobiegaj odświeżaniu"
REFRESH_PREVENTION_HELP: "Użyj unikatowego identyfikatora ID, aby upewnić się, że przeglądarka nie przetworzy formularza ponownie."
LIMIT: "Limit"
LIMIT_HELP: "Maksymalna ilość dozwolonych plików na pole (wymagana liczba mnoga)"
FILESIZE: "Maksymalny rozmiar pliku"
FILESIZE_HELP: "Maksymalny dozwolony rozmiar pliku (w MB), 0 = użyj ustawień systemowych"
AVOID_OVERWRITING: "Unikaj nadpisywania"
AVOID_OVERWRITING_HELP: "Unikaj nadpisywania plików o tej samej nazwie. Zostanie dodany prefiks z datą"
RANDOM_NAME: "Losowa nazwa"
RANDOM_NAME_HELP: "Wygeneruj losowy ciąg 15 znaków jako nazwę dla przesyłanych plików"
CLIENT_SIDE_VALIDATION: "Weryfikacja po stronie klienta"
CLIENT_SIDE_VALIDATION_HELP: "Domyślnie formularze użyją weryfikacji HTML5 po stronie klienta jako pierwszej linii obrony"
INLINE_ERRORS: "Błędy inline"
INLINE_ERRORS_HELP: "Przydatne przy wyłączonej weryfikacji po stronie klienta, wyświetla kontekstowe błędy formularza"
RESOLUTION_MIN: "{{attr}} była mniejsza od minimum wynoszącego {{min}}px <br />"
RESOLUTION_MAX: "{{attr}} była większa od maksimum wynoszącego {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "Cel nie został określony"
INVALID_MIME_TYPE: "Typ MIME %s dla pliku %s nie jest dozwolony."
INVALID_FILE_EXTENSION: "Rozszerzenie pliku %s nie jest dozwolone."
nl:
PLUGIN_FORM:
NOT_VALIDATED: "Formulier niet gevalideerd. Een of meerdere verplichte velden ontbreken."
NONCE_NOT_VALIDATED: "Oeps er was een probleem, controleer je input en stuur het formulier opnieuw."
FILES: "Bestanden geupload"
FORM_ALREADY_SUBMITTED: "Dit formulier is al eerder verstuurd."
ALLOW_MULTIPLE: "Meer dan een bestand toegestaan"
ALLOW_MULTIPLE_HELP: "Staat toe dat meer dan een bestand geselecteerd kan worden voor uploaden."
DESTINATION: "Bestemming"
DESTINATION_HELP: "De lokatie waar de bestanden naartoe moeten worden geupload"
ACCEPT: "Toegestane MIME types"
ACCEPT_HELP: "Een lijst van MIME types welke zijn toegestaan om te uploaden"
ERROR_VALIDATING_CAPTCHA: "Fout bij Captcha validatie"
DATA_SUMMARY: "Hier is de samenvatting van wat u ons heeft gestuurd:"
NO_FORM_DATA: "Geen formdata beschikbaar"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Site sleutel"
RECAPTCHA_SITE_KEY_HELP: "Voor meer informatie ga naar https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Geheime sleutel"
RECAPTCHA_SECRET_KEY_HELP: "Voor meer informatie ga naar https://developers.google.com/recaptcha"
GENERAL: "Algemeen"
USE_BUILT_IN_CSS: "Gebruik ingebouwde CSS"
FILEUPLOAD_PREVENT_SELF: 'Kan "%s" niet buiten paginas gebruiken.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Niet mogelijk om bestand %s: %s te uploaden'
FILEUPLOAD_UNABLE_TO_MOVE: 'Niet mogelijk om bestand %s naar "%s" te verplaatsen'
DROPZONE_CANCEL_UPLOAD: 'Upload annuleren'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Weet u zeker dat u deze upload wilt annuleren?'
DROPZONE_DEFAULT_MESSAGE: 'Sleep hier uw bestanden heen of <strong>klik in dit gebied</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Uw browser ondersteund geen drag en drop bestand uploads.'
DROPZONE_FALLBACK_TEXT: 'Gebruik alsublieft het fallback formulier hieronder om uw bestanden te uploaden zoals vroeger.'
DROPZONE_FILE_TOO_BIG: 'Bestand is te groot ({{filesize}}MiB). Maximale bestandsgrootte: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "U kunt geen bestanden uploaden van dit type."
DROPZONE_MAX_FILES_EXCEEDED: "U kunt niet meer bestanden uploaden."
DROPZONE_REMOVE_FILE: "Verwijder bestand"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Weet u zeker dat u het bestand wilt verwijderen?'
DROPZONE_RESPONSE_ERROR: "Server reageert met {{statusCode}} code."
YES: "Ja"
NO: "Nee"
REFRESH_PREVENTION: "Verversbescherming"
REFRESH_PREVENTION_HELP: "Gebruik het unieke ID van her formulier om zeker te stellen dat hetzelfde formulier niet opnieuw wordt verwerkt als de browser ververst wordt"
LIMIT: "Limiet"
LIMIT_HELP: "Maximum aantal toegestande bestanden per veld (meerdere vereist)"
FILESIZE: "Maximale bestandsgrootte"
FILESIZE_HELP: "Maximale bestandsgrootte toegestaan (in MB), 0 = Gebruik systeem standaard"
AVOID_OVERWRITING: "Vermijd overschrijven"
AVOID_OVERWRITING_HELP: "Voorkom dat bestanden met dezelfde naam worden overschreven. Datum prefix wordt toegevoegd"
RANDOM_NAME: "Willekeurige naam"
RANDOM_NAME_HELP: "Genereer een willekeurige naam van 15 karakters voor de geuploade bestanden"
pt:
PLUGIN_FORM:
NOT_VALIDATED: "Formulário não validado. Falta preencher um ou mais campos obrigatórios."
NONCE_NOT_VALIDATED: "Oops, houve um problema. Por favor verifique os dados informados e envie o formulário novamente."
FILES: "Envio de Arquivos"
FORM_ALREADY_SUBMITTED: "Este formulário já foi enviado."
ALLOW_MULTIPLE: "Permitir mais de um arquivo"
ALLOW_MULTIPLE_HELP: "Permite selecionar mais de um arquivo para enviar."
DESTINATION: "Destino"
DESTINATION_HELP: "O local para onde os arquivos devem ser enviados"
ACCEPT: "Tipos MIME Permitidos"
ACCEPT_HELP: "Uma lista de tipos MIME permitidos no upload"
ERROR_VALIDATING_CAPTCHA: "Erro ao validar o captcha"
DATA_SUMMARY: "Aqui está um resumo do que você escreveu:"
NO_FORM_DATA: "Nenhum dado de formulário disponível"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "Para mais informações visite https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Secret key"
RECAPTCHA_SECRET_KEY_HELP: "Para mais informações visite https://developers.google.com/recaptcha"
GENERAL: "Geral"
USE_BUILT_IN_CSS: "Usar CSS built-in"
USE_INLINE_CSS: "Usar CSS inline"
FILEUPLOAD_PREVENT_SELF: 'Não é permitido usar "%s" fora de páginas.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Não foi possível enviar o arquivo %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Não foi possível mover o arquivo %s para "%s"'
DROPZONE_CANCEL_UPLOAD: 'Cancelar envio'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Tem certeza de que deseja cancelar este envio?'
DROPZONE_DEFAULT_MESSAGE: 'Arraste seus arquivos para cá ou <strong>clique nesta área</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Seu navegador não suporta arrastar e soltar arquivos.'
DROPZONE_FALLBACK_TEXT: 'Por favor use o formulário abaixo para enviar seus arquivos como antigamente.'
DROPZONE_FILE_TOO_BIG: 'O arquivo é muito grande ({{filesize}}MiB). Tamanho máximo: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Você não pode enviar arquivos deste tipo."
DROPZONE_MAX_FILES_EXCEEDED: "Você não pode enviar mais arquivos."
DROPZONE_REMOVE_FILE: "Remover arquivo"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Tem certeza de que deseja excluir este arquivo?'
DROPZONE_RESPONSE_ERROR: "O servidor respondeu com código {{statusCode}}."
YES: "Sim"
NO: "Não"
REFRESH_PREVENTION: "Evitar recarregamento"
REFRESH_PREVENTION_HELP: "Usar o ID do formulário para garantir que ele não seja reprocessado se o usuário recarregar a página"
LIMIT: "Limite"
LIMIT_HELP: "Número máximo de arquivos permitidos por campo (deve ser múltiplo)"
FILESIZE: "Tamanho máximo"
FILESIZE_HELP: "Tamanho máximo de arquivo permitido (em MB), 0 = Use o default do sistema"
AVOID_OVERWRITING: "Evitar sobrescrita"
AVOID_OVERWRITING_HELP: "Prevenir que arquivos com o mesmo nome sejam sobrescritos. Serão adicionados prefixos com a data"
RANDOM_NAME: "Nome aleatório"
RANDOM_NAME_HELP: "Gerar um nome aleatório de 15 caracteres para os arquivos recebidos"
CLIENT_SIDE_VALIDATION: "Validação no cliente"
CLIENT_SIDE_VALIDATION_HELP: "Por padrão os formulários usarão a validação HTML5 no cliente como primeira linha de defesa"
INLINE_ERRORS: "Erros inline"
INLINE_ERRORS_HELP: "Útil se a validação no cliente estiver desabilitada. Mostra os erros próximo aos respectivos campos"
RESOLUTION_MIN: "A {{attr}} é menor que o mínimo de {{min}}px <br />"
RESOLUTION_MAX: "A {{attr}} é maior que o máximo de {{max}}px <br />"
da:
PLUGIN_FORM:
NOT_VALIDATED: "Formular ikke valideret. Et eller flere påkrævede felter mangler."
NONCE_NOT_VALIDATED: "Ups der var et problem, tjek venligst dit input og send formularen igen."
FILES: "Filupload"
FORM_ALREADY_SUBMITTED: "Denne formular er allerede blevet sendt."
ALLOW_MULTIPLE: "Tillad mere end én fil."
ALLOW_MULTIPLE_HELP: "Tillader at der vælges mere end én fil til upload."
DESTINATION: "Destination"
DESTINATION_HELP: "Lokationen hvor filerne skal uploades til"
ACCEPT: "Tilladte MIME Typer"
ACCEPT_HELP: "En liste af MIME Typer der er tilladte for upload"
ERROR_VALIDATING_CAPTCHA: "Fejl ved validering af Captcha"
DATA_SUMMARY: "Her er resuméet af det, du skrev til os:"
NO_FORM_DATA: "Ingen formular-data til rådighed"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Site nøgle"
RECAPTCHA_SITE_KEY_HELP: "For mere info besøg https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Hemmelig nøgle"
RECAPTCHA_SECRET_KEY_HELP: "For mere info besøg https://developers.google.com/recaptcha"
GENERAL: "Generel"
USE_BUILT_IN_CSS: "Brug indbygget CSS"
USE_INLINE_CSS: "Brug inline CSS"
FILEUPLOAD_PREVENT_SELF: 'Kan ikke bruge "%s" uden for sider.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Kan ikke uploade filen %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Kan ikke flytte fil %s til "%s"'
DROPZONE_CANCEL_UPLOAD: 'Annuller upload'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Er du sikker på, at du vil annullere denne upload?'
DROPZONE_DEFAULT_MESSAGE: 'Drop dine filer her eller <strong>klik i dette område</ strong>'
DROPZONE_FALLBACK_MESSAGE: 'Din browser understøtter ikke træk-og-slip filoploads.'
DROPZONE_FALLBACK_TEXT: 'Brug venligst nedenstående formular til at uploade dine filer som i de gamle dage.'
DROPZONE_FILE_TOO_BIG: 'Filen er for stor ({{filesize}}MiB). Maks. filstørrelse: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Du kan ikke uploade filer af denne type."
DROPZONE_MAX_FILES_EXCEEDED: "Du kan ikke uploade flere filer."
DROPZONE_REMOVE_FILE: "Fjern fil"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Er du sikker på, at du vil slette denne fil?'
DROPZONE_RESPONSE_ERROR: "Server svarede med {{statusCode}} kode."
YES: "Ja"
NO: "Nej"
REFRESH_PREVENTION: "Forebyg opdatering"
REFRESH_PREVENTION_HELP: "Brug formularens unikke ID for at sikre, at samme form ikke bearbejdes igen, når du opdaterer browseren"
LIMIT: "Begrænsning"
LIMIT_HELP: "Maksimum antal tilladte filer pr. felt (flere påkrævede)"
FILESIZE: "Maks. filstørrelse"
FILESIZE_HELP: "Maksimal filstørrelse tilladt (i MB), 0 = Brug systemstandard"
AVOID_OVERWRITING: "Undgå at overskrive"
AVOID_OVERWRITING_HELP: "Forhindre at filer med samme navn overskrives. Dato præfiks vil blive tilføjet"
RANDOM_NAME: "Tilfældigt navn"
RANDOM_NAME_HELP: "Generer et tilfældigt 15 tegn langt strengnavn for de uploadede filer"
CLIENT_SIDE_VALIDATION: "Validering på klientsiden"
CLIENT_SIDE_VALIDATION_HELP: "Som standard bruger formularer HTML5-klient validering som første forsvarslinje"
INLINE_ERRORS: "Inline fejl"
INLINE_ERRORS_HELP: "Nyttig med 'validering på klientsiden' deaktiveret, viser inline kontekstuelle form fejl"
RESOLUTION_MIN: "{{attr}} var mindre end minimum på {{min}}px <br />"
RESOLUTION_MAX: "{{attr}} var højere end maksimum på {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "Destination ikke angivet"
INVALID_MIME_TYPE: "MIME typen %s for filen %s er ikke accepteret."
INVALID_FILE_EXTENSION: "Filendelsen for filen %s er ikke accepteret."
+46
View File
@@ -0,0 +1,46 @@
{
"name": "grav-plugin-form",
"version": "1.0.0",
"description": "Grav Plugin Form",
"repository": "https://github.com/getgrav/grav-plugin-form",
"main": "app/main.js",
"scripts": {
"watch": "NODE_ENV=development webpack --watch --progress --colors --mode development --config webpack.conf.js",
"dev": "NODE_ENV=development webpack --progress --colors --config webpack.conf.js",
"prod": "NODE_ENV=production-wip webpack --mode development --config webpack.conf.js"
},
"author": "Team Grav",
"license": "MIT",
"dependencies": {
"dropzone": "^5.5.1",
"exif-js": "^2.3.0",
"sortablejs": "^1.7.0"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",
"babel-loader": "^7.1.5",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"css-loader": "^1.0.0",
"eslint": "^5.6.0",
"eslint-loader": "^2.1.1",
"exports-loader": "^0.7.0",
"gulp": "latest",
"gulp-autoprefixer": "latest",
"gulp-clean-css": "^3.10.0",
"gulp-csscomb": "^3.0.8",
"gulp-rename": "^1.4.0",
"gulp-sass": "^4.0.1",
"gulp-sourcemaps": "^2.6.4",
"gulp-webpack": "^1.5.0",
"immutable": "^4.0.0-rc.12",
"imports-loader": "^0.8.0",
"json-loader": "^0.5.7",
"style-loader": "^0.23.0",
"uglifyjs-webpack-plugin": "^2.0.1",
"webpack": "^4.19.1",
"webpack-cli": "^3.1.0"
}
}
+289
View File
@@ -0,0 +1,289 @@
$form-border-color: #eee;
$form-active-color: #000;
.form-group.has-errors {
background: rgba(255,0,0,0.05);
border: 1px solid rgba(255,0,0,0.2);
border-radius: 3px;
margin: 0 -5px;
padding: 0 5px;
}
.form-errors {
color: #b52b27;
}
.form-honeybear {
visibility: hidden;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
.form-errors p {
margin: 0;
}
.form-input-file {
input {
display: none;
}
.dz-default.dz-message {
position: absolute;
text-align: center;
left: 0;
right: 0;
top: 50%;
transform: translateY(-50%);
margin: 0;
}
&.dropzone {
position: relative;
min-height: 70px;
border-radius: 3px;
margin-bottom: .85rem;
border: 2px dashed #ccc;
color: #aaa;
padding: 0.5rem;
.dz-preview {
margin: 0.5rem;
&:hover {
z-index: 2;
}
.dz-error-message {
min-width: 140px;
width: auto;
}
.dz-image,
&.dz-file-preview .dz-image {
border-radius: 3px;
z-index: 1;
}
}
}
}
// New JS powered tabs
.form-tabs {
.tabs-nav {
display: flex;
padding-top: 1px;
margin-bottom: -1px;
a {
flex: 1;
transition: color 0.5s ease, background 0.5s ease;
cursor: pointer;
text-align:center;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid $form-border-color;
border-radius: 5px 5px 0 0;
&.active {
border: 1px solid $form-border-color;
border-bottom: 1px solid transparent;
margin: 0 -1px;
span {
color: $form-active-color;
}
}
}
span {
display: inline-block;
line-height: 1.1;
}
}
&.subtle .tabs-nav {
margin-right: 0 !important;
}
.tabs-content {
.tab__content {
display: none;
padding-top: 2rem;
&.active {
display: block;
}
}
}
}
// Checkboxes
.checkboxes {
display: inline-block;
label {
display: inline;
cursor: pointer;
position: relative;
padding: 0 0 0 20px;
margin-right: 15px;
}
label:before {
content:"";
display: inline-block;
width: 20px;
height: 20px;
left: 0;
margin-top: 0;
margin-right: 10px;
position: absolute;
border-radius: 3px;
border: 1px solid #e6e6e6;
}
input[type=checkbox] {
display: none;
}
input[type=checkbox]:checked + label:before {
content:"\2713";
font-size: 20px;
line-height: 1;
text-align: center;
}
&.toggleable label{
margin-right: 0;
}
}
// Toggleable
.form-field-toggleable {
.checkboxes.toggleable {
margin-right: 5px;
vertical-align: middle;
}
.checkboxes + label {
display: inline-block;
}
}
// Toggles
.switch-toggle {
display: inline-flex;
overflow: hidden;
border-radius: 3px;
line-height: 35px;
border: 1px solid $form-border-color;
input[type=radio] {
position: absolute;
visibility: hidden;
display: none;
}
label {
display: inline-block;
cursor: pointer;
padding: 0 15px;
margin: 0;
white-space: nowrap;
color: inherit;
transition: background-color 0.5s ease;
}
input.highlight:checked + label {
background: #333;
color: #fff;
}
input:checked + label {
color: #fff;
background: #999;
}
}
/* Signature Pad */
.signature-pad {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
font-size: 10px;
width: 100%;
height: 100%;
max-width: 700px;
max-height: 460px;
border: 1px solid #f0f0f0;
background-color: #fff;
padding: 16px;
}
.signature-pad--body {
position: relative;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
border: 1px solid #f6f6f6;
min-height: 100px;
}
.signature-pad--body canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
}
.signature-pad--footer {
color: #C3C3C3;
text-align: center;
font-size: 1.2em;
}
.signature-pad--actions {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
margin-top: 8px;
}
[data-grav-field="array"] .form-row {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
[data-grav-field="array"] .form-row > input,
[data-grav-field="array"] .form-row > textarea
{
margin: 0 0.5rem;
display: inline-block;
}
@@ -0,0 +1,2 @@
{% include 'partials/form-messages.html.twig' %}
{% do http_response_code(form.responseCode) %}
@@ -0,0 +1,2 @@
{% include 'partials/form-messages.json.twig' %}
{% do http_response_code(form.responseCode) %}
@@ -0,0 +1,8 @@
{% extends 'partials/base.html.twig' %}
{% block content %}
{{ content|raw }}
{% include "forms/form.html.twig" %}
{% endblock %}
@@ -0,0 +1 @@
{% extends 'forms/ajax.json.twig' %}
@@ -0,0 +1,20 @@
{% extends 'partials/base.html.twig' %}
{% if form is null %}
{% set form = grav.session.getFlashObject('form') %}
{% endif %}
{% block content %}
{{ content|raw }}
{% if form %}
{% include 'partials/form-messages.html.twig' %}
<p>{{ 'PLUGIN_FORM.DATA_SUMMARY'|t }}</p>
{% include "forms/data.html.twig" %}
{% else %}
<div class="notices warning yellow"><p>{{ 'PLUGIN_FORM.NO_FORM_DATA'|t }}</p></div>
{% endif %}
{% endblock %}
@@ -0,0 +1,5 @@
{% if form_json_response %}
{{ form_json_response|json_encode|raw }}
{% else %}
{}
{% endif %}
@@ -0,0 +1 @@
{% extends "forms/default/data.html.twig" %}
@@ -0,0 +1 @@
{% extends "forms/default/data.txt.twig" %}
@@ -0,0 +1,48 @@
{% macro render_field(form, fields, scope) %}
{% import _self as self %}
{% for index, field in fields %}
{% set input = attribute(field, "input@") %}
{% if input is null or input == true %}
{% if form.value(scope ~ field.name) %}
{% block field %}
<div>
{% block field_label %}
<strong>{{ field.label|t|e }}</strong>:
{% endblock %}
{% block field_value %}
{% if field.type == 'checkboxes' %}
<ul>
{% set use_keys = field.use is defined and field.use == 'keys' %}
{% for key,value in form.value(scope ~ field.name) %}
{% set index = (use_keys ? key : value) %}
<li>{{ field.options[index]|e }}</li>
{% endfor %}
</ul>
{% elseif field.type == 'checkbox' %}
{{ (form.value(scope ~ field.name) == 1) ? "GRAV.YES"|t|e : "GRAV.NO"|t|e }}
{% elseif field.type == 'select' %}
{{ field.options[form.value(scope ~ field.name)]|e }}
{% else %}
{{ string(form.value(scope ~ field.name))|nl2br }}
{% endif %}
{% endblock %}
</div>
{% endblock %}
{% endif %}
{% else %}
{% if field.fields %}
{% set new_scope = field.nest_id ? scope ~ field.name ~ '.' : scope %}
{{ self.render_field(form, field.fields, new_scope) }}
{% endif %}
{% endif %}
{% endfor %}
{% endmacro %}
{% import _self as macro %}
{{ macro.render_field(form, form.fields, '') }}
@@ -0,0 +1,19 @@
{%- macro render_field(form, fields, scope) %}
{%- import _self as self %}
{%- for index, field in fields %}
{%- set input = attribute(field, "input@") %}
{%- if input is null or input == true %}
{%- set value = form.value(scope ~ (field.name ?? index)) %}
{{- scope ~ (field.name ?? index) }}: {{ string(value is iterable ? value|json_encode : value|escape('yaml')) ~ "\r\n" }}
{%- else %}
{%- if field.fields %}
{%- set new_scope = field.nest_id ? scope ~ field.name ~ '.' : scope -%}
{{- self.render_field(form, field.fields, new_scope) }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endmacro %}
{%- import _self as macro %}
{%- autoescape false %}
{{- macro.render_field(form, form.fields, '') ~ "\r\n" }}
{%- endautoescape %}
@@ -0,0 +1,139 @@
{% if not field.validate.ignore %}
{% set field_name = (scope ~ field.name)|fieldName %}
{% set vertical = field.style == 'vertical' %}
{% if not blueprints or (blueprints.schema.type(field.type)['input@'] ?? true) is same as(true) %}
{% set toggleable = field.toggleable ?? false %}
{% if toggleable %}
{% set originalValue = originalValue is defined ? originalValue : value %}
{% set toggleableChecked = originalValue is not null and originalValue is not empty %}
{% elseif field.overridable %}
{% set toggleable = true %}
{% set default = form.getDefaultValue(field.name) ?? field.default %}
{% set toggleableChecked = value != default %}
{% endif %}
{% set cookie_name = 'forms-' ~ form.name ~ '-' ~ field.name %}
{% set value = value ?? (get_cookie(cookie_name) is not null ? get_cookie(cookie_name) : field.default) %}
{% if (field.yaml or field.validate.type == 'yaml') and value is iterable %}
{% set value = value|toYaml %}
{% endif %}
{% else %}
{% set toggleable = false %}
{% endif %}
{# DEPRECATED: Needed by old form fields; remove when backwards compatibility breaks are allowed #}
{% set isDisabledToggleable = toggleable and not toggleableChecked %}
{% set errors = attribute(form.messages, field.name) %}
{% set required = client_side_validation and field.validate.required in ['on', 'true', 1] %}
{% set autofocus = (inline_errors == false) and field.autofocus in ['on', 'true', 1] %}
{% if inline_errors and errors %}
{% set autofocus = true %}
{% endif %}
{% block field %}
<div class="form-field {{ form_field_outer_classes }} {{ field.outerclasses }} {% if errors %} has-errors{% endif %} {% block outer_field_classes %}{% endblock %} {% if toggleable %} form-field-toggleable{% endif %}">
{% block contents %}
{% if field.label is not same as(false) and field.display_label is not same as(false) %}
<div class="{{ form_field_outer_label_classes ?: 'form-label' }} {{ field.labelclasses }}">
{% if toggleable %}
{% include 'forms/default/toggleable.html.twig' with {field_name: field_name, field: field, checked: toggleableChecked} %}
{% endif %}
<label class="{{ form_field_label_classes ?: 'inline' }} {{ toggleable ? 'toggleable' }}" {% if field.id is defined %}for="{{ toggleable ? 'toggleable_' ~ field.name : field.id|e }}" {% endif %} >
{% block label %}
{% if field.help %}
{% if field.markdown %}
<span class="tooltip" data-asTooltip-position="w" title="{{ field.help|t|markdown(false)|e }}">{{ field.label|markdown(false)|default(field.name|capitalize)|t }}</span>
{% else %}
<span class="tooltip" data-asTooltip-position="w" title="{{ field.help|t|e }}">{{ field.label|default(field.name|capitalize)|t }}</span>
{% endif %}
{% else %}
{% if field.markdown %}
{{ field.label|markdown(false)|default(field.name|capitalize)|t }}
{% else %}
{{ field.label|default(field.name|capitalize)|t|e('html_attr') }}
{% endif %}
{% endif %}
{{ field.validate.required in ['on', 'true', 1] ? '<span class="required">*</span>' }}
{% endblock %}
</label>
</div>
{% endif %}
<div class="{{ form_field_outer_data_classes ?: 'form-data' }} {{ field.dataclasses }}"
{% block global_attributes %}
data-grav-field="{{ field.type }}"
data-grav-disabled="{{ toggleable and toggleableChecked }}"
data-grav-default="{{ field.default|json_encode()|e('html_attr') }}"
{% endblock %}
>
{% block group %}
{% block input %}
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
{% block prepend %}{% endblock prepend %}
<input
{# required attribute structures #}
name="{{ (scope ~ field.name)|fieldName }}"
value="{{ value|join(', ')|e('html_attr') }}"
{# input attribute structures #}
{% block input_attributes %}
class="{{ form_field_input_classes }} {{ field.classes }} {{ field.size }}"
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.placeholder %}placeholder="{{ field.placeholder|t|e('html_attr') }}"{% endif %}
{% if autofocus %}autofocus="autofocus"{% endif %}
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if field.readonly in ['on', 'true', 1] %}readonly="readonly"{% endif %}
{% if field.autocomplete is defined %}autocomplete="{{ field.autocomplete }}"{% endif %}
{% if field.autocapitalize in ['off', 'characters', 'words', 'sentences'] %}autocapitalize="{{ field.autocapitalize }}"{% endif %}
{% if field.inputmode in ['none', 'text', 'decimal', 'numeric', 'tel', 'search', 'email', 'url'] %}inputmode="{{ field.inputmode }}"{% endif %}
{% if field.spellcheck in ['true', 'false'] %}spellcheck="{{ field.spellcheck }}"{% endif %}
{% if field.attributes is defined %}
{% for attribute in field.attributes %}
{{ attribute.name }}="{{ attribute.value|e }}"
{% endfor %}
{% endif %}
{% if required %}required="required"{% endif %}
{% if field.validate.pattern %}pattern="{{ field.validate.pattern|e }}"{% endif %}
{% if field.validate.message %}title="{{ field.validate.message|t|e }}"
{% elseif field.title is defined %}title="{{ field.title|t|e }}" {% endif %}
{% if field.datasets %}
{% for datakey, datavalue in field.datasets %}
data-{{ datakey }}="{{ datavalue|e('html_attr') }}"
{% endfor %}
{% endif %}
{% endblock %}
/>
{% block append %}{% endblock append %}
{% if inline_errors and errors %}
<div class="{{ form_errors_classes ?: 'form-errors' }}">
<p class="form-message"><i class="fa fa-exclamation-circle"></i> {{ errors|first }}</p>
</div>
{% endif %}
</div>
{% endblock %}
{% endblock %}
{% if field.description %}
<div class="form-extra-wrapper {{ field.wrapper_classes }}">
<span class="form-description">
{% if field.markdown %}
{{ field.description|t|markdown(false)|raw }}
{% else %}
{{ field.description|t|raw }}
{% endif %}
</span>
</div>
{% endif %}
</div>
{% endblock %}
</div>
{% endblock %}
{% endif %}
@@ -0,0 +1,15 @@
{% for field_name, field in fields %}
{% if field.type and not field.validate.ignore %}
{%- if field_name starts with '.' -%}
{% set field_name = name ? name ~ field_name : field_name[1:] %}
{% set field = field|merge({ name: field_name }) %}
{% endif %}
{% set value = form ? form.value(field_name) : data.value(field_name) %}
{% block field_open %}{% endblock %}
{% block field %}
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
{% endblock %}
{% block field_close %}{% endblock %}
{% endif %}
{% endfor %}
@@ -0,0 +1,134 @@
{% if form is null %}
{% set form = grav.session.getFlashObject('form') %}
{% endif %}
{% include 'partials/form-messages.html.twig' %}
{% set scope = scope ?: 'data.' %}
{% set multipart = '' %}
{% set blueprints = blueprints ?? form.blueprint() %}
{% set method = form.method|upper|default('POST') %}
{% set client_side_validation = form.client_side_validation is not null ? form.client_side_validation : config.plugins.form.client_side_validation|default(true) %}
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
{% for field in form.fields %}
{% if (method == 'POST' and field.type == 'file') %}
{% set multipart = ' enctype="multipart/form-data"' %}
{% endif %}
{% endfor %}
{% set action = form.action ?: page.route ~ uri.params %}
{% set action = action starts with 'http' ? action : base_url ~ action %}
{% if (action == base_url_relative) %}
{% set action = base_url_relative ~ '/' ~ page.slug %}
{% endif %}
{% if form.keep_alive %}
{% if grav.browser.browser == 'msie' and grav.browser.version < 12 %}
{% do assets.addJs('plugin://form/assets/object.assign.polyfill.js') %}
{% endif %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% endif %}
{% do assets.addInlineJs("
window.GravForm = window.GravForm || {};
window.GravForm.config = {
current_url: '" ~ uri.route(true) ~"',
base_url_relative: '" ~ base_url_relative ~ "',
param_sep: '"~ config.system.param_sep ~ "',
form_nonce: '" ~ form.getNonce() ~ "',
session_timeout: " ~ config.system.session.timeout ~ "
};
window.GravForm.translations = Object.assign({}, window.GravForm.translations || {}, { PLUGIN_FORM: {} });
", {'group': 'bottom', 'position': 'before'}) %}
<form name="{{ form.name }}"
action="{{ action | trim('/', 'right') }}"
method="{{ method }}"{{ multipart }}
{% if form.id %}id="{{ form.id }}"{% endif %}
{% block form_classes %}
class="{{ form_outer_classes }} {{ form.classes }}"
{% endblock %}
{% if form.novalidate %}novalidate{% endif %}
{% if form.keep_alive %}data-grav-keepalive="true"{% endif %}
>
{% block inner_markup_fields_start %}{% endblock %}
{% set data = data ?? form.data %}
{% set context = context ?? data %}
{% block inner_markup_fields %}
{% for field_name, field in form.fields %}
{% set field_name = field.name ?? field_name %}
{% if field_name and not field.validate.ignore %}
{%- if field_name starts with '.' -%}
{% set field_name = field_name[1:] %}
{% set field = field|merge({ name: field_name }) %}
{% endif %}
{% set value = form ? form.value(field_name) : data.value(field_name) %}
{% block inner_markup_field_open %}{% endblock %}
{% block process_field %}
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" ignore missing %}
{% endblock %}
{% block inner_markup_field_close %}{% endblock %}
{% endif %}
{% endfor %}
{% endblock %}
{% include "forms/fields/formname/formname.html.twig" %}
{% include "forms/fields/formtask/formtask.html.twig" %}
{% block inner_markup_fields_end %}{% endblock %}
{% block inner_markup_buttons_start %}
<div class="{{ form_button_outer_classes ?: 'buttons'}}">
{% endblock %}
{% for button in form.buttons %}
{% if button.outerclasses is defined %}<div class=" {{ button.outerclasses }}">{% endif %}
{% if button.url %}
<a href="{{ button.url starts with 'http' ? button.url : base_url ~ button.url }}">
{% endif %}
<button
{% if button.id %}id="{{ button.id }}"{% endif %}
{% block button_classes %}
class="{{ form_button_classes ?: 'button' }} {{ button.classes }}"
{% endblock %}
{% if button.disabled %}disabled="disabled"{% endif %}
type="{{ button.type|default('submit') }}"
{% if button.task %}
name="task" value="{{ button.task }}"
{% endif %}
>
{% if button.html %}
{{ button.value|t|default('Submit')|raw }}
{% else %}
{{ button.value|t|default('Submit') }}
{% endif %}
</button>
{% if button.url %}
</a>
{% endif %}
{% if button.outerclasses is defined %}</div>{% endif %}
{% endfor %}
{% block inner_markup_buttons_end %}
</div>
{% endblock %}
{% include 'forms/fields/uniqueid/uniqueid.html.twig' %}
{{ nonce_field(form.getNonceAction() ?? 'form', form.getNonceName() ?? 'form-nonce')|raw }}
</form>
{% if config.forms.dropzone.enabled %}
<div id="dropzone-template" style="display:none;">
{% include 'forms/dropzone/template.html.twig' %}
</div>
{% endif %}
@@ -0,0 +1,9 @@
<span class="checkboxes toggleable" data-grav-field="toggleable" data-grav-field-name="{{ field_name }}">
<input type="checkbox"
id="toggleable_{{ field.name }}"
name="toggleable_{{ field_name }}"
value="1"
{% if checked %}checked="checked"{% endif %}
>
<label for="toggleable_{{ field.name }}"></label>
</span>
@@ -0,0 +1,39 @@
<div class="dz-preview dz-file-preview">
<div class="dz-image"><img data-dz-thumbnail /></div>
<div class="dz-details">
<div class="dz-size"><span data-dz-size></span></div>
<div class="dz-filename"><span data-dz-name></span></div>
</div>
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
<div class="dz-error-message"><span data-dz-errormessage></span></div>
<a class="dz-remove" title="remove" href="javascript:undefined;" data-dz-remove>&times;</a>
<div class="dz-success-mark">
<svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.1 (9971) - http://www.bohemiancoding.com/sketch -->
<title>Check</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF" sketch:type="MSShapeGroup"></path>
</g>
</svg>
</div>
<div class="dz-error-mark">
<svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.1 (9971) - http://www.bohemiancoding.com/sketch -->
<title>error</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="Check-+-Oval-2" sketch:type="MSLayerGroup" stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475">
<path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" sketch:type="MSShapeGroup"></path>
</g>
</g>
</svg>
</div>
</div>
@@ -0,0 +1 @@
{% extends "forms/default/field.html.twig" %}
@@ -0,0 +1,99 @@
{% extends "forms/field.html.twig" %}
{% macro renderer(key, text, field, scope) %}
{% if text is not iterable %}
<div class="form-row{% if field.value_only %} array-field-value_only{% endif %}"
data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
{% if field.value_only != true %}
{% if key == '0' and text == '' %}
{% set key = '' %}
{% endif %}
<input
data-grav-array-type="key"
type="text" value="{{ key }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_key|e|t }}" />
{% endif %}
{% if field.value_type == 'textarea' %}
<textarea
data-grav-array-type="value"
name="{{ ((scope ~ field.name)|fieldName) ~ '[' ~ key ~ ']' }}"
placeholder="{{ field.placeholder_value|e|t }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}>{{ text }}</textarea>
{% else %}
<input
data-grav-array-type="value"
type="text"
name="{{ ((scope ~ field.name)|fieldName) ~ '[' ~ key ~ ']' }}"
placeholder="{{ field.placeholder_value|e|t }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
value={% if text == 'true' %}true{% elseif text == 'false' %}false{% else %}"{{ text|join(', ')|e }}"{% endif %} />
{% endif %}
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>
{% endif %}
{% endmacro %}
{% import _self as array_field %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% block global_attributes %}
data-grav-array-name="{{ (scope ~ field.name)|fieldName }}"
data-grav-array-keyname="{{ field.placeholder_key|e|t }}"
data-grav-array-valuename="{{ field.placeholder_value|e|t }}"
data-grav-array-textarea="{{ field.value_type == 'textarea' }}"
{{ parent() }}
{% endblock %}
{% block input %}
<div class="{{ field.size }} {{ field.classes }}" data-grav-array-type="container"{% if field.value_only %} data-grav-array-mode="value_only"{% endif %}{{ value|length <= 1 ? ' class="one-child"' : '' }}>
{% if value|length %}
{% for key, text in value -%}
{% if text is not iterable %}
{{ array_field.renderer(key, text, field, scope) }}
{% else %}
{# Backward compatibility for nested arrays (metas) which are not supported anymore #}
{% for subkey, subtext in text -%}
{{ array_field.renderer(key ~ '[' ~ subkey ~ ']', subtext, field, scope) }}
{% endfor %}
{% endif %}
{% endfor %}
{%- else -%}
{# Empty value, mock the entry field#}
<div class="form-row" data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
{% if field.value_only != true %}
<input
data-grav-array-type="key"
type="text"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_key|e|t }}" />
{% endif %}
{% if field.value_type == 'textarea' %}
<textarea
data-grav-array-type="value"
name="{{ (scope ~ field.name)|fieldName }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_value|e|t }}"></textarea>
{% else %}
<input
data-grav-array-type="value"
type="text"
name="{{ (scope ~ field.name)|fieldName }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_value|e|t }}" />
{% endif %}
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>
{%- endif %}
</div>
{% endblock %}
@@ -0,0 +1,10 @@
{% extends "forms/field.html.twig" %}
{% block field %}
{% set avatar = form.value('avatar') %}
{% if avatar %}
<label><img style="max-width:200px;" src="{{ base_url_simple ~ '/' ~ (avatar|first).path }}" /></label>
{% else %}
<label><img src="https://www.gravatar.com/avatar/{{ form.value('email')|md5 }}?s=200" /></label>
{% endif %}
{% endblock %}
@@ -0,0 +1,96 @@
{% extends "forms/field.html.twig" %}
{% set config = grav.config %}
{% set site_key = field.recaptcha_site_key and field.recaptcha_site_key != 'ENTER_YOUR_CAPTCHA_SITE_KEY' ? field.recaptcha_site_key : config.plugins.form.recaptcha.site_key %}
{% set action = (page.route|trim('/') ~ '-' ~ form.name)|underscorize %}
{% set formName = form.name|underscorize %}
{% set theme = config.plugins.form.recaptcha.theme ?? 'light' %}
{% block label %}{% endblock %}
{% block input %}
{% if not site_key %}
<script>console && console.error('site_key was not defined for form "{{ form.name }}" (Grav Form Plugin)')</script>
{% elseif config.plugins.form.recaptcha.version == 3 %}
{#{% do assets.addJs('https://www.google.com/recaptcha/api.js?render='~site_key) %}#}
<script src='https://www.google.com/recaptcha/api.js?render={{ site_key }}&theme={{ theme }}'></script>
<script>
(document.querySelectorAll('form') || []).forEach(function(form, index) {
var submit = function(event) {
event.preventDefault();
grecaptcha.ready(function () {
grecaptcha.execute('{{ site_key }}', { action: '{{ action }}' }).then(function(token) {
var tokenElement = document.createElement('input');
tokenElement.setAttribute('type', 'hidden');
tokenElement.setAttribute('name', 'data[token]');
tokenElement.setAttribute('value', token);
var actionElement = document.createElement('input');
actionElement.setAttribute('type', 'hidden');
actionElement.setAttribute('name', 'data[action]');
actionElement.setAttribute('value', '{{ action }}');
form.insertBefore(tokenElement, form.firstChild);
form.insertBefore(actionElement, form.firstChild);
form.removeEventListener('submit', submit);
form.submit();
});
});
};
form.addEventListener('submit', submit);
});
</script>
{% elseif config.plugins.form.recaptcha.version == '2-invisible' %}
<script>
function captchaOnloadCallback_{{ formName }}() {
var form = document.querySelector('form#{{ form.id }}');
var submits = form.querySelectorAll('[type="submit"]') || [];
submits.forEach(function(submit) {
submit.addEventListener('click', function(event) {
event.preventDefault();
var captchaElement = form.querySelector('#g-recaptcha-{{ formName }}');
if (captchaElement) {
captchaElement.remove();
}
captchaElement = document.createElement('div');
captchaElement.setAttribute('id', 'g-recaptcha-{{ formName }}');
form.appendChild(captchaElement);
var widgetReference = grecaptcha.render('g-recaptcha-{{ formName }}', {
sitekey: '{{ site_key }}', size: 'invisible',
callback: function(/* token */) {
form.submit();
}
});
grecaptcha.execute(widgetReference);
});
});
}
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=captchaOnloadCallback_{{ formName }}&hl={{ grav.language.language }}&theme={{ theme }}"
async defer></script>
{% else %}
<script>
var captchaOnloadCallback_{{ formName }} = function captchaOnloadCallback_{{ formName }}() {
grecaptcha.render('g-recaptcha-{{ formName }}', {
'sitekey': "{{ site_key }}",
'callback': captchaValidatedCallback_{{ formName }},
'expired-callback': captchaExpiredCallback_{{ formName }}
});
};
var captchaValidatedCallback_{{ formName }} = function captchaValidatedCallback_{{ formName }}() {};
var captchaExpiredCallback_{{ formName }} = function captchaExpiredCallback_{{ formName }}() {
grecaptcha.reset();
};
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=captchaOnloadCallback_{{ formName }}&render=explicit&hl={{ grav.language.language }}&theme={{ theme }} "
async defer></script>
<div class="g-recaptcha" id="g-recaptcha-{{ formName }}"></div>
{% endif %}
{% endblock %}
@@ -0,0 +1,36 @@
{% extends "forms/field.html.twig" %}
{% block label %}
{% endblock %}
{% block input %}
{% set id = field.id|default(field.name) ~ '-' ~ key %}
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
<input
{# required attribute structures #}
name="{{ (scope ~ field.name)|fieldName }}"
value="{{ value|join(', ') }}"
type="checkbox"
{% if value == true %} checked="checked" {% endif %}
{# input attribute structures #}
{% block input_attributes %}
id="{{ id|e }}"
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if required %}required="required"{% endif %}
{% endblock %}
/>
<label style="display:inline;" class="inline" for="{{ id|e }}">
{% if field.markdown %}
{{ field.label|t|markdown(false) }}
{% else %}
{{ field.label|t|e }}
{% endif %}
{{ field.validate.required in ['on', 'true', 1] ? '<span class="required">*</span>' }}
</label>
</div>
{% endblock %}
@@ -0,0 +1,43 @@
{% extends "forms/field.html.twig" %}
{% set originalValue = value %}
{% set value = (value is null ? field.default : value) %}
{% if field.use == 'keys' and field.default %}
{% set value = field.default|merge(value) %}
{% endif %}
{% block global_attributes %}
{{ parent() }}
data-grav-keys="{{ field.use == 'keys' ? 'true' : 'false' }}"
data-grav-field-name="{{ (scope ~ field.name)|fieldName }}"
{% endblock %}
{% block input %}
{% for key, text in field.options %}
{% set id = field.id|default(field.name) ~ '-' ~ key %}
{% set name = field.use == 'keys' ? key : id %}
{% set val = field.use == 'keys' ? '1' : key %}
{% set checked = (field.use == 'keys' ? value[key] : key in value) %}
{% set help = (key in field.help_options|keys ? field.help_options[key] : false) %}
<div class="checkboxes {{ form_field_wrapper_classes }} {{ field.wrapper_classes }}">
<input type="checkbox"
id="{{ id|e }}"
value="{{ val|e }}"
name="{{ (scope ~ field.name)|fieldName ~ '[' ~ name ~ ']' }}"
{% if checked %}checked="checked"{% endif %}
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
>
<label style="display: inline" for="{{ id|e }}">
{% if help %}
<span class="hint--bottom" data-hint="{{ help|t|e('html_attr') }}">{{ text|t|e }}</span>
{% else %}
{{ text|t|e }}
{% endif %}
</label>
</div>
{% endfor %}
{% endblock %}
@@ -0,0 +1,6 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="color"
{{ parent() }}
{% endblock %}
@@ -0,0 +1,9 @@
{% extends "forms/field.html.twig" %}
{% block field %}
{% if field.fields %}
<div class="form-column {{ field.classes }}">
{% include 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
</div>
{% endif %}
{% endblock %}
@@ -0,0 +1,9 @@
{% extends "forms/field.html.twig" %}
{% block field %}
<div class="form-columns {{ field.classes }}">
{% if field.fields %}
{% include 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1,21 @@
{% extends "forms/field.html.twig" %}
{% block field %}
{% set value = evaluate(field.condition) %}
{% set value = value == 'true' ? 1 : value %}
{% set value = value == 'false' ? 0 : value %}
{% if value %}
{% if field.classes %}
<div class="{{ field.classes }}">
{% endif %}
{% if field.fields %}
{% include 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
{% endif %}
{% if field.classes %}
</div>
{% endif %}
{% endif %}
{% endblock %}
@@ -0,0 +1,8 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="date"
{% if field.validate.min %}min="{{ field.validate.min }}"{% endif %}
{% if field.validate.max %}max="{{ field.validate.max }}"{% endif %}
{{ parent() }}
{% endblock %}
@@ -0,0 +1 @@
{{ value ? value|date('m/d/Y')|e }}
@@ -0,0 +1,2 @@
{# DEPRECATED. Switched to Text field until implemented properly #}
{% extends "forms/fields/text/text.html.twig" %}
@@ -0,0 +1 @@
{{ value ? value|date('m/d/Y \\a\\t g:i A')|e }}
@@ -0,0 +1,17 @@
{% extends "forms/field.html.twig" %}
{% if field.file %}
{% set content = read_file(field.file) %}
{% else %}
{% set content = field.content %}
{% endif %}
{% block input %}
<div class="form-display-wrapper {{ field.size }} {{ field.classes }}" {% if field.id is defined %}id="{{ field.id|e }}" {% endif %}>
{% if field.markdown %}
{{ content|t|markdown|raw }}
{% else %}
{{ content|t|raw }}
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1,7 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="email"
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
{{ parent() }}
{% endblock %}
@@ -0,0 +1,14 @@
{% extends "forms/field.html.twig" %}
{% set scope = field.nest_id ? scope ~ field.name ~ '.' : scope %}
{% block field %}
<fieldset {% if field.id is defined %}id="{{ field.id }}"{% endif %} {% if field.classes is defined %}class="{{ field.classes }}" {% endif %}>
{% if field.legend %}
<legend>{{ field.legend|t }}</legend>
{% endif %}
{% if field.fields %}
{% include 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
{% endif %}
</fieldset>
{% endblock %}
@@ -0,0 +1,119 @@
{% extends "forms/field.html.twig" %}
{% macro bytesToSize(bytes) -%}
{% spaceless %}
{% set kilobyte = 1024 %}
{% set megabyte = kilobyte * 1024 %}
{% set gigabyte = megabyte * 1024 %}
{% set terabyte = gigabyte * 1024 %}
{% if bytes < kilobyte %}
{{ bytes ~ ' B' }}
{% elseif bytes < megabyte %}
{{ (bytes / kilobyte)|number_format(2, '.') ~ ' KB' }}
{% elseif bytes < gigabyte %}
{{ (bytes / megabyte)|number_format(2, '.') ~ ' MB' }}
{% elseif bytes < terabyte %}
{{ (bytes / gigabyte)|number_format(2, '.') ~ ' GB' }}
{% else %}
{{ (bytes / terabyte)|number_format(2, '.') ~ ' TB' }}
{% endif %}
{% endspaceless %}
{%- endmacro %}
{% macro preview(path, value, global) %}
{% if value %}
{% set uri = global.grav.uri %}
{% set files = global.files %}
{% set config = global.grav.config %}
{% set route = global.context.route() %}
{% set type = global.context.content() is not null ? 'pages' : global.plugin ? 'plugins' : global.theme ? 'themes' : 'config' %}
{% set blueprint_name = global.blueprints.getFilename %}
{% if type == 'pages' %}
{% set blueprint_name = type ~ '/' ~ blueprint_name %}
{% endif %}
{% set blueprint = base64_encode(blueprint_name) %}
{% set real_path = value.thumb ?? global.context.media[path].relativePath ?? global.form.getPagePathFromToken(path) %}
{% set remove = global.form.getFileDeleteAjaxRoute(files.name, path)|string ?: uri.addNonce(
global.base_url_relative ~
'/media.json' ~
'/route' ~ config.system.param_sep ~ base64_encode(global.base_path ~ '/' ~ real_path) ~
'/task' ~ config.system.param_sep ~ 'removeFileFromBlueprint' ~
'/proute' ~ config.system.param_sep ~ base64_encode(route) ~
'/blueprint' ~ config.system.param_sep ~ blueprint ~
'/type' ~ config.system.param_sep ~ type ~
'/field' ~ config.system.param_sep ~ files.name ~
'/path' ~ config.system.param_sep ~ base64_encode(value.path), 'admin-form', 'admin-nonce') %}
{% set file = value|merge({remove: remove, path: value.thumb_url ?? (uri.rootUrl == '/' ? '/' : uri.rootUrl ~ '/' ~ real_path) }) %}
<div class="hidden" data-file="{{ file|json_encode|e('html_attr') }}"></div>
{% endif %}
{% endmacro %}
{% import _self as macro %}
{% set defaults = config.plugins.form %}
{% set files = defaults.files|merge(field|default([])) %}
{% set limit = not field.multiple ? 1 : files.limit %}
{% do config.set('forms.dropzone.enabled', true) %}
{% block input %}
{% set page_can_upload = exists or (type == 'page' and not exists and not (field.destination starts with '@self' or field.destination starts with 'self@')) %}
{% set max_filesize = (field.filesize > form_max_filesize or field.filesize == 0) ? form_max_filesize : field.filesize %}
{% block prepend %}{% endblock %}
{% set settings = {name: field.name, paramName: (scope ~ field.name)|fieldName ~ (files.multiple ? '[]' : ''), limit: limit, filesize: max_filesize, accept: files.accept, resolution: files.resolution, resizeWidth: files.resizeWidth, resizeHeight: files.resizeHeight, resizeQuality: files.resizeQuality } %}
{% set dropzoneSettings = field.dropzone %}
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} {{ field.classes }} dropzone files-upload form-input-file {{ field.size }}" data-grav-file-settings="{{ settings|json_encode|e('html_attr') }}" data-dropzone-options="{{ dropzoneSettings|json_encode|e('html_attr') }}" {% if form.getFileUploadAjaxRoute %}data-file-url-add="{{ form.getFileUploadAjaxRoute() }}"{% endif %}>
{% block file_extras %}{% endblock %}
<input
{# required attribute structures #}
{% block input_attributes %}
type="file"
{% if files.multiple %}multiple="multiple"{% endif %}
{% if files.accept %}accept="{{ files.accept|join(',') }}"{% endif %}
{% if field.disabled %}disabled="disabled"{% endif %}
{% if field.random_name %}random="true"{% endif %}
{% if required %}required="required"{% endif %}
{{ parent() }}
{% endblock %}
/>
{% for path, file in value %}
{{ macro.preview(path, file, _context) }}
{% endfor %}
{% include 'forms/fields/hidden/hidden.html.twig' with {field: {name: '_json.' ~ field.name}, value: (value ?? [])|json_encode } %}
</div>
{% if grav.browser.browser == 'msie' and grav.browser.version < 12 %}
{% do assets.addJs('plugin://form/assets/object.assign.polyfill.js') %}
{% endif %}
{% do assets.addJs('jquery', 101) %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addCss('plugin://form/assets/dropzone.min.css', { 'group': 'form'}) %}
{{ assets.css('form')|raw }}
{% do assets.addInlineJs("
window.GravForm = window.GravForm || {};
window.GravForm = Object.assign({}, window.GravForm, {
translations: {
PLUGIN_FORM: {
'DROPZONE_CANCEL_UPLOAD': " ~ 'PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD'|t|json_encode ~ ",
'DROPZONE_CANCEL_UPLOAD_CONFIRMATION': " ~ 'PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD_CONFIRMATION'|t|json_encode ~ ",
'DROPZONE_DEFAULT_MESSAGE': " ~ 'PLUGIN_FORM.DROPZONE_DEFAULT_MESSAGE'|t|json_encode ~ ",
'DROPZONE_FALLBACK_MESSAGE': " ~ 'PLUGIN_FORM.DROPZONE_FALLBACK_MESSAGE'|t|json_encode ~ ",
'DROPZONE_FALLBACK_TEXT': " ~ 'PLUGIN_FORM.DROPZONE_FALLBACK_TEXT'|t|json_encode ~ ",
'DROPZONE_FILE_TOO_BIG': " ~ 'PLUGIN_FORM.DROPZONE_FILE_TOO_BIG'|t|json_encode ~ ",
'DROPZONE_INVALID_FILE_TYPE': " ~ 'PLUGIN_FORM.DROPZONE_INVALID_FILE_TYPE'|t|json_encode ~ ",
'DROPZONE_MAX_FILES_EXCEEDED': " ~ 'PLUGIN_FORM.DROPZONE_MAX_FILES_EXCEEDED'|t|json_encode ~ ",
'DROPZONE_REMOVE_FILE': " ~ 'PLUGIN_FORM.DROPZONE_REMOVE_FILE'|t|json_encode ~ ",
'DROPZONE_REMOVE_FILE_CONFIRMATION': " ~ 'PLUGIN_FORM.DROPZONE_REMOVE_FILE_CONFIRMATION'|t|json_encode ~ ",
'DROPZONE_RESPONSE_ERROR': " ~ 'PLUGIN_FORM.DROPZONE_RESPONSE_ERROR'|t|json_encode ~ ",
'RESOLUTION_MIN': " ~ 'PLUGIN_FORM.RESOLUTION_MIN'|t|json_encode ~ ",
'RESOLUTION_MAX': " ~ 'PLUGIN_FORM.RESOLUTION_MAX'|t|json_encode ~ "
}
}
});
", {'group': 'bottom', 'position': 'before'}) %}
{% endblock %}
@@ -0,0 +1,5 @@
{% extends "forms/field.html.twig" %}
{% block field %}
<input type="hidden" name="__form-name__" value="{{ form.name }}" />
{% endblock %}
@@ -0,0 +1,8 @@
{% extends "forms/field.html.twig" %}
{% block field %}
{% if form.task %}
<input type="hidden" name="task" value="{{ form.task }}" />
{% endif %}
{% endblock %}
@@ -0,0 +1,9 @@
{% extends "forms/field.html.twig" %}
{% block field %}
{% set value = value ?: field.value|default(field.default) %}
{% set value = field.evaluate ? evaluate(value) : value %}
<input data-grav-field="hidden" data-grav-disabled="false" type="hidden" class="input" name="{{ (scope ~ field.name)|fieldName }}" value="{{ value|join(', ')|e('html_attr') }}" />
{% endblock %}
@@ -0,0 +1,12 @@
{% extends "forms/field.html.twig" %}
{% block field %}
<input aria-hidden="true"
type="text"
{% if config.plugins.form.inline_css == true %}
style="visibility:hidden;position:absolute!important;height:1px;width:1px;overflow:hidden;clip:rect(1px,1px,1px,1px);"
{% endif %}
class="form-honeybear"
name="{{ (scope ~ field.name)|fieldName }}"
value="{{ value|join(', ') }}" />
{% endblock %}
@@ -0,0 +1,26 @@
{% extends "forms/field.html.twig" %}
{% block input %}
<div class="form-input-wrapper {{ field.size }}">
<input
type="text"
value="{{ value|e('html_attr')|join(', ') }}"
data-key-observe="{{ (scope ~ field.name)|fieldName }}"
{% block input_attributes %}
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.placeholder %}placeholder="{{ field.placeholder }}"{% endif %}
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if field.readonly in ['on', 'true', 1] %}readonly="readonly"{% endif %}
{% if field.autocomplete in ['on', 'off'] %}autocomplete="{{ field.autocomplete }}"{% endif %}
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
{% if field.validate.pattern %}pattern="{{ field.validate.pattern }}"{% endif %}
{% if field.validate.message %}title="{{ field.validate.message|e|t }}"
{% elseif field.title is defined %}title="{{ field.title|e|t }}" {% endif %}
{% endblock %}
/>
</div>
{% endblock %}
@@ -0,0 +1,6 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="month"
{{ parent() }}
{% endblock %}
@@ -0,0 +1,9 @@
{% extends "forms/fields/text/text.html.twig" %}
{% block input_attributes %}
type="number"
{% if field.validate.min is defined %}min="{{ field.validate.min }}"{% endif %}
{% if field.validate.max is defined %}max="{{ field.validate.max }}"{% endif %}
{% if field.validate.step is defined %}step="{{ field.validate.step }}"{% endif %}
{{ parent() }}
{% endblock %}
@@ -0,0 +1,6 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="password"
{{ parent() }}
{% endblock %}
@@ -0,0 +1,23 @@
{% extends "forms/field.html.twig" %}
{% set originalValue = value %}
{% set value = (value is null ? field.default : value) %}
{% block input %}
{% for key, text in field.options %}
{% set id = field.id|default(field.name) ~ '-' ~ key %}
<div class="radio {{ form_field_wrapper_classes }} {{ field.wrapper_classes }}">
<input type="radio"
value="{{ key|e }}"
id="{{ id|e }}"
name="{{ (scope ~ field.name)|fieldName }}"
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
{% if key == value %}checked="checked" {% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if required %}required="required"{% endif %}
/>
<label style="display: inline" class="inline" for="{{ id|e }}">{{ text|t|raw }}</label>
</div>
{% endfor %}
{% endblock %}
@@ -0,0 +1,9 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="range"
{% if field.validate.min %}min="{{ field.validate.min }}"{% endif %}
{% if field.validate.max %}max="{{ field.validate.max }}"{% endif %}
{% if field.validate.step %}step="{{ field.validate.step }}"{% endif %}
{{ parent() }}
{% endblock %}
@@ -0,0 +1,21 @@
{% extends "forms/field.html.twig" %}
{% block field %}
{% if field.security is empty or authorize(array(field.security)) %}
<div class="{{ field.classes }}">
{% if field.title or field.underline %}
<h1 class="{% if not field.underline %}no_underline{% endif %}">{{ field.title|t }}</h1>
{% endif %}
{% if field.text %}
<p>{{ field.text|t|raw }}</p>
{% endif %}
{% if field.fields %}
<div class="form-section">
{% include 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
</div>
{% endif %}
</div>
{% endif %}
{% endblock %}
@@ -0,0 +1,67 @@
{% extends "forms/field.html.twig" %}
{% block global_attributes %}
data-grav-selectize="{{ (field.selectize is defined ? field.selectize : {})|json_encode()|e('html_attr') }}"
{{ parent() }}
{% endblock %}
{% block input %}
<div class="{{ form_field_wrapper_classes ?: 'form-select-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
<select name="{{ (scope ~ field.name)|fieldName ~ (field.multiple ? '[]' : '') }}"
class="{{ form_field_select_classes }} {{ field.classes }} {{ field.size }}"
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
{% if field.disabled %}disabled="disabled"{% endif %}
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if required %}required="required"{% endif %}
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.form %}form="{{ field.form }}"{% endif %}
{% if field.key %}
data-key-observe="{{ (scope ~ field.name)|fieldName }}"
{% endif %}
{% if field.datasets %}
{% for datakey, datavalue in field.datasets %}
data-{{ datakey }}="{{ datavalue|e('html_attr') }}"
{% endfor %}
{% endif %}
>
{% if field.placeholder %}<option value="" disabled selected>{{ field.placeholder|t|raw }}</option>{% endif %}
{% set options = field.options %}
{% if field.selectize.create and field.multiple %}
{% set options = options|merge(value|default([]))|array_unique %}
{% endif %}
{% for key, item_value in options %}
{% if item_value is iterable and item_value.value %}
{% set akey = field.selectize and field.multiple ? item_value : key %}
{% set avalue = item_value.value|t %}
<option {{ item_value.disabled ? 'disabled="disabled"' : '' }}
{{ item_value.selected ? 'selected="selected"' : '' }}
{{ item_value.label ? 'label=' ~ item_value.label : '' }}
value="{{ item_value.akey }}"
>
{{ avalue|raw }}
</option>
{% elseif item_value is iterable %}
<optgroup label="{{ key }}">
{%for subkey, suboption in item_value %}
{% set selected = field.selectize ? suboption : subkey %}
{% set item_value = field.selectize and field.multiple ? suboption : subkey %}
<option {% if subkey == value or (field.multiple and selected in value) %}selected="selected"{% endif %} value="{{ suboption }}">
{{ suboption|t|raw }}
</option>
{% endfor %}
</optgroup>
{% else %}
{% set selected = field.selectize ? item_value : key %}
{% set val = field.selectize and field.multiple ? item_value : key %}
<option {% if val == value or (field.multiple and selected in value) %}selected="selected"{% endif %} value="{{ val }}">{{ item_value|t|raw }}</option>
{% endif %}
{% endfor %}
</select>
</div>
{% endblock %}
@@ -0,0 +1,42 @@
{% extends "forms/field.html.twig" %}
{% block global_attributes %}
data-grav-selectize="{{ (field.selectize is defined ? field.selectize : {})|json_encode()|e('html_attr') }}"
{{ parent() }}
{% endblock %}
{% block input %}
<div class="{{ form_field_wrapper_classes ?: 'form-select-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
<select name="{{ (scope ~ field.name)|fieldName ~ (field.multiple ? '[]' : '') }}"
class="{{ form_field_select_classes }} {{ field.classes }}"
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
{% if field.disabled %}disabled="disabled"{% endif %}
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if required %}required="required"{% endif %}
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.form %}form="{{ field.form }}"{% endif %}
{% if field.key %}
data-key-observe="{{ (scope ~ field.name)|fieldName }}"
{% endif %}
>
{% if field.placeholder %}<option value="" disabled selected>{{ field.placeholder|t|raw }}</option>{% endif %}
{% for key, optgroup in field.options %}
{% set optgroup_label = optgroup|keys|first %}
<optgroup label="{{ optgroup_label }}">
{%for subkey, suboption in field.options[key][optgroup_label] %}
{% set selected = field.selectize ? suboption : subkey %}
{% set item_value = field.selectize and field.multiple ? suboption : subkey %}
<option {% if subkey == value or (field.multiple and selected in value) %}selected="selected"{% endif %} value="{{ suboption }}">
{{ suboption|t|raw }}
</option>
{% endfor %}
</optgroup>
{% endfor %}
</select>
</div>
{% endblock %}
@@ -0,0 +1,105 @@
{% extends "forms/field.html.twig" %}
{% block prepend %}
<div id="signature-pad" class="signature-pad">
<div class="signature-pad--body">
<canvas></canvas>
</div>
<div class="signature-pad--footer">
<div class="description">Sign above</div>
<div class="signature-pad--actions">
<div>
<button type="button" class="btn btn-primary-border" data-action="clear">Clear Signature</button>
</div>
</div>
</div>
</div>
<script src="{{ url('plugin://form/assets/signature_pad.js') }}"></script>
<script>
var wrapper = document.getElementById("signature-pad");
var clearButton = wrapper.querySelector("[data-action=clear]");
var canvas = wrapper.querySelector("canvas");
var signaturePad = new SignaturePad(canvas, {
// It's Necessary to use an opaque color when saving image as JPEG;
// this option can be omitted if only saving as PNG or SVG
backgroundColor: 'rgb(255, 255, 255)',
onEnd: function() {
var input = document.querySelector('[name="data[{{ field.name }}]"]');
input.value = this.toDataURL();
}
});
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
function resizeCanvas() {
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
var ratio = Math.max(window.devicePixelRatio || 1, 1);
// This part causes the canvas to be cleared
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
// This library does not listen for canvas changes, so after the canvas is automatically
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
// that the state of this library is consistent with visual state of the canvas, you
// have to clear it manually.
signaturePad.clear();
}
// On mobile devices it might make more sense to listen to orientation change,
// rather than window resize events.
window.onresize = resizeCanvas;
resizeCanvas();
function download(dataURL, filename) {
var blob = dataURLToBlob(dataURL);
var url = window.URL.createObjectURL(blob);
var a = document.createElement("a");
a.style = "display: none";
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
}
// One could simply use Canvas#toBlob method instead, but it's just to show
// that it can be done using result of SignaturePad#toDataURL.
function dataURLToBlob(dataURL) {
// Code taken from https://github.com/ebidel/filer.js
var parts = dataURL.split(';base64,');
var contentType = parts[0].split(":")[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], { type: contentType });
}
clearButton.addEventListener("click", function (event) {
signaturePad.clear();
});
</script>
{% endblock prepend %}
{% block input_attributes %}
type="hidden"
{{ parent() }}
{% endblock %}
@@ -0,0 +1,19 @@
{% extends "forms/field.html.twig" %}
{% block field %}
<div class="form-spacer {{ field.classes }}">
{% if field.title %}
<h3>{{- field.title|t|raw -}}</h3>
{% endif %}
{% if field.markdown %}
<p>{{- field.text|t|markdown|raw -}}</p>
{% else %}
<p>{{- field.text|t|raw -}}</p>
{% endif %}
{% if field.underline %}
<hr />
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1 @@
{% extends 'forms/fields/checkbox/checkbox.html.twig' %}
@@ -0,0 +1,9 @@
{% extends "forms/field.html.twig" %}
{% block field %}
{% if field.fields %}
<div class="form-tab">
{% include 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
</div>
{% endif %}
{% endblock %}
@@ -0,0 +1,66 @@
{% extends "forms/field.html.twig" %}
{% if grav.admin is not defined %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% endif %}
{% block field %}
<div class="form-tabs {{ field.class }} {{ field.classes }}">
{% if field.fields %}
{% set tabs = {} %}
{% for tab in field.fields %}
{% if tab.type == 'tab' and not tab.validate.ignore and (tab.security is empty or authorize(array(tab.security))) %}
{% set tabs = tabs|merge([tab]) %}
{% endif %}
{% endfor %}
{% set count = tabs|count %}
{% if count == 0 %}
{# Nothing to display #}
{% elseif count == 1 %}
{% for field in (tabs|first).fields %}
{% if field.type %}
{% set value = field.name ? (form ? form.value(field.name) : data.value(field.name)) : data.toArray %}
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
{% endif %}
{% endfor %}
{% else %}
{% set tabsKey = field.fields|keys|join('.') %}
{% set storedValue = grav.admin is defined ? get_cookie('grav-tabs-state')|default('{}')|json_decode : [] %}
{% set storedTab = attribute(storedValue, 'tab-' ~ tabsKey) %}
{% if storedTab is empty %}
{% if uri.params.tab %}
{% set active = uri.params.tab %}
{% elseif field.active %}
{% set active = field.active %}
{% else %}
{% set active = 1 %}
{% endif %}
{% endif %}
<div class="tabs-nav">
{% for tab in tabs %}
{% if tab.type == 'tab' %}
<a class="tab__link {{ (storedTab == scope ~ tab.name) or active == loop.index ? 'active' : '' }}" data-tabid="tab-{{ tabsKey ~ loop.index }}" data-tabkey="tab-{{ tabsKey }}" data-scope="{{ scope ~ tab.name }}">
<span>{{ tab.title|t }}</span>
{% endif %}
</a>
{% endfor %}
</div>
<div class="tabs-content">
{% embed 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
{% block field_open %}
<div id="tab-{{ tabsKey ~ loop.index }}" class="tab__content {{ (storedTab == scope ~ field.name) or active == loop.index ? 'active' : '' }}">
{% endblock %}
{% block field_close %}
</div>
{% endblock %}
{% endembed %}
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1,6 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="tel"
{{ parent() }}
{% endblock %}
@@ -0,0 +1,5 @@
{% if link -%}
<a href="{{ link }}">{{ value|join(', ')|e }}</a>
{%- else -%}
{{ value|join(', ')|e }}
{%- endif %}
@@ -0,0 +1,41 @@
{% extends "forms/field.html.twig" %}
{% block prepend %}
{% if field.prepend %}
<div class="form-input-addon form-input-prepend">
{{- field.prepend|t|raw -}}
</div>
{% endif %}
{% endblock %}
{% block input_attributes %}
type="text"
{% if field.size %}size="{{ field.size }}"{% endif %}
{% if field.minlength is defined or field.validate.min is defined %}minlength="{{ field.minlength | default(field.validate.min) }}"{% endif %}
{% if field.maxlength is defined or field.validate.max is defined %}maxlength="{{ field.maxlength | default(field.validate.max) }}"{% endif %}
{{ parent() }}
{% endblock %}
{% block append %}
{% if field.copy_to_clipboard %}
<div class="form-input-addon form-input-append copy-to-clipboard">
{{ dump(field.copy_to_clipboard|t|raw) }}
{% if field.copy_to_clipboard in ['0', '1'] %}
<i class="fa fa-clipboard"></i>
{% else %}
{{- field.copy_to_clipboard|t|raw -}}
{% endif %}
</div>
{% elseif field.append %}
<div class="form-input-addon form-input-append">
{{- field.append|t|raw -}}
</div>
{% endif %}
{% endblock %}
{% block input %}
{% if field.prepend or field.append or field.copy_to_clipboard %}
{% set field = field|merge({'wrapper_classes': 'form-input-addon-wrapper'}) %}
{% endif %}
{{ parent() }}
{% endblock %}
@@ -0,0 +1,41 @@
{% extends "forms/field.html.twig" %}
{% block input %}
<div class="{{ form_field_wrapper_classes ?: 'form-textarea-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
{% block prepend %}{% endblock prepend %}
<textarea
{# required attribute structures #}
name="{{ (scope ~ field.name)|fieldName }}"
{# input attribute structures #}
{% block input_attributes %}
class="{{ form_field_textarea_classes }} {{ field.classes }} {{ field.size }}"
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.placeholder %}placeholder="{{ field.placeholder|t }}"{% endif %}
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
{% if field.readonly in ['on', 'true', 1] %}readonly="readonly"{% endif %}
{% if field.autocomplete in ['on', 'off'] %}autocomplete="{{ field.autocomplete }}"{% endif %}
{% if required %}required="required"{% endif %}
{% if field.validate.pattern %}pattern="{{ field.validate.pattern }}"{% endif %}
{% if field.validate.message %}title="{{ field.validate.message|t|e }}"{% endif %}
{% if field.rows is defined %}rows="{{ field.rows }}"{% endif %}
{% if field.cols is defined %}cols="{{ field.cols }}"{% endif %}
{% if field.minlength is defined %}minlength="{{ field.minlength }}"{% endif %}
{% if field.maxlength is defined %}maxlength="{{ field.maxlength }}"{% endif %}
{% if field.datasets %}
{% for datakey, datavalue in field.datasets %}
data-{{ datakey }}="{{ datavalue|e('html_attr') }}"
{% endfor %}
{% endif %}
{% endblock %}
>{{ value|trim|e('html') }}</textarea>
{% block append %}{% endblock append %}
{% if inline_errors and errors %}
<div class="{{ form_errors_classes ?: 'form-errors' }}">
<p class="form-message"><i class="fa fa-exclamation-circle"></i> {{ errors|first }}</p>
</div>
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1 @@
{{ value ? value|date('g:i A')|e }}
@@ -0,0 +1,6 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="time"
{{ parent() }}
{% endblock %}
@@ -0,0 +1,5 @@
{%- if value -%}
<span><i class="published fa fa-check-circle"></i></span>
{%- else -%}
<span><i class="unpublished fa fa-times-circle"></i></span>
{%- endif -%}
@@ -0,0 +1,62 @@
{% extends "forms/field.html.twig" %}
{% macro spanToggle(input, length) %}
{% set space = repeat('&nbsp;&nbsp;', (length - input|length) / 2) %}
{{ (space ~ input ~ space)|raw }}
{% endmacro %}
{% import _self as macro %}
{% set value = value ?? field.default %}
{% set value = (value is same as(false) ? 0 : value) %}
{% set has_hidden = false %}
{% for key, text in field.options %}
{% if key is empty %}
{% set has_hidden = true %}
{% endif %}
{% endfor %}
{% block global_attributes %}
{{ parent() }}
data-grav-field-name="{{ (scope ~ field.name)|fieldName }}"
{% endblock %}
{% block input %}
<div class="switch-toggle switch-grav {{ field.size }} switch-{{ field.options|length }} {{ field.classes }}">
{% set maxLen = 0 %}
{% for text in field.options %}
{% set translation = text|t|trim %}
{% set maxLen = max(translation|length, maxLen) %}
{% endfor %}
{% for key, text in field.options %}
{% set id = "toggle_" ~ field.name ~ key %}
{% set translation = text|t|trim %}
<input type="radio"
value="{{ key }}"
id="{{ id }}"
name="{{ (scope ~ field.name)|fieldName }}"
{% if field.highlight is defined %}
class="{{ field.highlight == '' ~ key ? 'highlight' : '' }}"
{% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
{% if field.toggleable %}
{% if '' ~ key == '' ~ value %}
checked="checked"
{% elseif value is defined and (key == 1 or key == '1') %}
checked="checked"
{% endif %}
{% else %}
{% if '' ~ key == '' ~ value %}
checked="checked"
{% endif %}
{% endif %}
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
/>
<label for="{{ id }}">{{ (macro.spanToggle(translation, maxLen)|trim)|raw }}</label>
{% endfor %}
</div>
{% endblock %}
@@ -0,0 +1,5 @@
{% extends "forms/field.html.twig" %}
{% block field %}
<input type="hidden" name="__unique_form_id__" value="{{ form.uniqueid() ?? random_string(20) }}" />
{% endblock %}
@@ -0,0 +1 @@
<a href="{{ url(value) }}" target="_blank"><i class="fa fa-link"></i>{{ value|e }}</a>
@@ -0,0 +1,6 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="url"
{{ parent() }}
{% endblock %}
@@ -0,0 +1,18 @@
{% extends "forms/field.html.twig" %}
{% if field.options %}
{% set value = field.options[value] ?: value %}
{% endif %}
{% block input %}
<span class="field-value">
{% switch field.filter %}
{% case 'date' %}
{{ value|date }}
{% case 'raw' %}
{{ value|raw }}
{% default %}
{{ value }}
{% endswitch %}
</span>
{% endblock %}
@@ -0,0 +1,6 @@
{% extends "forms/field.html.twig" %}
{% block input_attributes %}
type="week"
{{ parent() }}
{% endblock %}
@@ -0,0 +1 @@
{% extends "forms/default/form.html.twig" %}
@@ -0,0 +1,4 @@
<div class="modular-row form {{ page.header.class }}">
{{ content|raw }}
{% include "forms/form.html.twig" %}
</div>

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