diff --git a/.gitignore b/.gitignore index 8b093469de16a3ff5b64c9f6ea98afcbe51247e0..a582212e0689392793bb1889e7e27a221769b68c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ .phpunit.result.cache nbprojec nohup.out +.DS_Store # Created by .ignore support plugin (hsz.mobi) .python_virtualenv/ @@ -71,6 +72,8 @@ composer.lock /javascript/src/.vscode /javascript/src/npm-debug.log +/javascript/build/dist +/less/dist /docker/chromedriver /docker/geckodriver diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a8be0e21a3d3c10f537ea57a595e820867dea4e8..6bc7ffb4ac638c7896dbd5b9ed7098500ecc7fed 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,8 @@ variables: stages: - before - build -# - selenium + - test + # - selenium #documentation: # stage: before @@ -29,7 +30,7 @@ snapshot: paths: - build/ script: - - make VERSION=${VERSION} phpunit_snapshot + - make VERSION=${VERSION} snapshot - chmod a+r qfq_${VERSION}_*.zip - echo "mv qfq_${VERSION}_*.zip qfq_${VERSION}_${RELDATE}-${CI_COMMIT_REF_NAME}.zip" - mv qfq_${VERSION}_*.zip qfq_${VERSION}_${RELDATE}-${CI_COMMIT_REF_NAME}.zip @@ -45,11 +46,17 @@ release: paths: - build/ script: - - make VERSION=${VERSION} phpunit_release + - make VERSION=${VERSION} release - chmod a+r qfq_${VERSION}_*.zip - scp qfq_${VERSION}_*.zip w16:qfq/releases/ - mv qfq_${VERSION}_*.zip build/qfq.zip +tests: + stage: test + script: + - make phpunit + + #selenium: # stage: selenium # script: diff --git a/Documentation/Form.rst b/Documentation/Form.rst index 52703cbd410dea3a02558565df532db6fdbe1e77..2829a8c80f19ff6d4a322017900e7942595769c1 100644 --- a/Documentation/Form.rst +++ b/Documentation/Form.rst @@ -1079,8 +1079,9 @@ FormElement.parameter | retypeNote, | | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | characterCountWrap, | See :ref:`input-text` | -| hideZero, | | -| emptyMeansNull, | | +| hideZero | | ++---------------------------------+----------------------------------------------------------------------------------------------------------+ +| emptyMeansNull | Applies to FormElement types (input, checkbox, radio, select, ...). See :ref:`input-text` | +---------------------------------+----------------------------------------------------------------------------------------------------------+ | showSeconds | 0|1 - Shows the seconds on form load. Default: 0 | +---------------------------------+----------------------------------------------------------------------------------------------------------+ diff --git a/Makefile b/Makefile index af23e110dc44860f4ea30f1f9ecc97f955a88046..86c102eec88d07024354f6d5ef3b942ef27b10b0 100644 --- a/Makefile +++ b/Makefile @@ -59,14 +59,15 @@ plantuml: cd doc/diagram ; $(MAKE) bootstrap: .npmpackages .plantuml_install .virtual_env - npm update - grunt default + npm install + npm run build # take care that phpOffice is located under 'qfq/Resources/Private/vendor/phpoffice' # cd extension/Resources/Private; composer update cd extension; composer update basic: .npmpackages .virtual_env - grunt default + npm install + npm run build # IMPORTANT: install composer with no-dev flag for deployment! cd extension; composer install --no-dev --optimize-autoloader; cd vendor/phpoffice/phpspreadsheet; rm -rf .github bin docs samples .g* .s* .t* C* c* m* p* @@ -81,7 +82,6 @@ basic: .npmpackages .virtual_env node --version which node echo "${PATH}" - npm ls -g grunt-cli 2>/dev/null || { echo "Please install grunt-cli npm package using 'npm install -g grunt-cli'" 1>&2 ; exit 1; } # update npm at persistent location and copy node_modules (to speed up process) mkdir -p $(VAR_TMP)/npm /bin/cp package.json $(VAR_TMP)/npm/ diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php index 50c069825f3b5447e812a1ad5ef6b057abb6166a..074a1b500de75e4db279c035fb97b77b92ee455a 100644 --- a/extension/Classes/Core/AbstractBuildForm.php +++ b/extension/Classes/Core/AbstractBuildForm.php @@ -394,7 +394,7 @@ abstract class AbstractBuildForm { $formArray[F_TITLE] = $this->formSpec[F_UNEVALUATED_TITLE]; $evaluatedTitle = $this->evaluate->parseArray($formArray); // If form title has changed, add new title to JSON - if($this->formSpec[F_TITLE] != $evaluatedTitle[F_TITLE]){ + if ($this->formSpec[F_TITLE] != $evaluatedTitle[F_TITLE]) { $element = array( 'form-element' => 'qfq-form-title', 'value' => $evaluatedTitle[F_TITLE] @@ -926,7 +926,7 @@ abstract class AbstractBuildForm { case FE_TYPE_DATE: case FE_TYPE_DATETIME: case FE_TYPE_TIME: - $elementHtml = DateTime::buildDateTime($formElement, $htmlFormElementName, $value, $jsonElement, $this->formSpec, $this->store, $mode, $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS]); + $elementHtml = DateTime::buildDateTime($formElement, $htmlFormElementName, $value, $jsonElement, $this->formSpec, $this->store, $mode, $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS]); //needed for datepicker to be positioned correctly if ($flagMulti == true) { $elementHtml = Support::wrapTag('<div class="col-d-12 col-lg-12">', $elementHtml); @@ -1232,7 +1232,7 @@ abstract class AbstractBuildForm { * @throws \CodeException * @throws \UserFormException */ - public static function getFormElementForJson(string $htmlFormElementName, $value, array $formElement, $wrap = '', $optionIdx = 0, $optionClass = ''): array { + public static function getFormElementForJson(string $htmlFormElementName, $value, array $formElement, $wrap = '', $optionIdx = 0, $optionClass = ''): array { $addClassRequired = array(); $json = HelperFormElement::getJsonFeMode($formElement[FE_MODE]); // disabled, required @@ -1559,7 +1559,7 @@ abstract class AbstractBuildForm { } // when size empty but value contains \n then set auto multi line - if(!isset($formElement[FE_TYPEAHEAD_SQL])){ + if (!isset($formElement[FE_TYPEAHEAD_SQL])) { if (strpos($value, "\n") == true && empty($formElement[FE_SIZE])) { $formElement[FE_SIZE] = '50,2'; } @@ -2123,7 +2123,7 @@ abstract class AbstractBuildForm { $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; $json = array_merge($this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass)); - $json[API_ELEMENT_UPDATE] = array_merge($json[API_ELEMENT_UPDATE], $jsonTmp[API_ELEMENT_UPDATE]); + $json[API_ELEMENT_UPDATE] = array_merge($json[API_ELEMENT_UPDATE], ($jsonTmp[API_ELEMENT_UPDATE] ?? array())); return $html; @@ -2517,7 +2517,7 @@ abstract class AbstractBuildForm { } $htmlBody .= Support::wrapTag("<tr $rowAttribute>", $rowHtml, true); } - if(empty($formElement[$subrecordToken])){ + if (empty($formElement[$subrecordToken])) { $rowHtml .= Support::wrapTag("<td>", $formElement[SUBRECORD_EMPTY_TEXT] ?? SUBRECORD_DEFAULT_EMPTY_TEXT); $htmlBody .= Support::wrapTag("<tr>", $rowHtml, true); } @@ -3057,7 +3057,7 @@ abstract class AbstractBuildForm { // API calls don't recognize paths like '/fileadmin/protected/...' if (!$testValue && isset($_GET["submit_reason"])) { - $value = $_SERVER["DOCUMENT_ROOT"] . '/'. $value; + $value = $_SERVER["DOCUMENT_ROOT"] . '/' . $value; $testValue = file_exists($value); } @@ -3538,7 +3538,7 @@ abstract class AbstractBuildForm { "file_picker_types" => "file image media", "image_advtab" => true, "automatic_uploads" => true, - "images_upload_url" => $baseUrl . Path::appToApi(API_FILE_PHP) . $completeUrl, + "images_upload_url" => $baseUrl . Path::appToApi(API_FILE_PHP) . $completeUrl, "images_reuse_filename" => true, "paste_data_images" => true ]; diff --git a/extension/Classes/Core/Helper/HelperFile.php b/extension/Classes/Core/Helper/HelperFile.php index b709f90ea2a7d26cf7574bff9da92811720add6d..2c27f5da010f883d254f1efae787edca0f4eb7b3 100644 --- a/extension/Classes/Core/Helper/HelperFile.php +++ b/extension/Classes/Core/Helper/HelperFile.php @@ -589,9 +589,9 @@ class HelperFile { * @param $content * @throws \UserFormException */ - public static function file_put_contents($pathFileName, $content) // : void + public static function file_put_contents($pathFileName, $content, $flag = 0) // : void { - $success = file_put_contents($pathFileName, $content); + $success = file_put_contents($pathFileName, $content, $flag); if ($success === false) { throw new \UserFormException(json_encode([ ERROR_MESSAGE_TO_USER => "Writing file failed.", diff --git a/extension/Classes/Core/Parser/KVPairListParser.php b/extension/Classes/Core/Parser/KVPairListParser.php index ca04529d6b99f4d0842ff6761a1d7de307931712..88a5eb8c57016c82f59a5b5b758306607940d609 100644 --- a/extension/Classes/Core/Parser/KVPairListParser.php +++ b/extension/Classes/Core/Parser/KVPairListParser.php @@ -72,7 +72,7 @@ class KVPairListParser extends SimpleParser { } // Replace an empty token with the empty value yield $key => $empty ? ( - $this->options[self::OPTION_KEY_IS_VALUE] ? $key : $this->options[self::OPTION_EMPTY_VALUE] + $this->options[self::OPTION_KEY_IS_VALUE] ? $key : $this->options[self::OPTION_EMPTY_VALUE] ) : $value; } elseif ($key) { // When no key-value separator, then do nothing it the key is empty diff --git a/extension/Classes/Core/Parser/SimpleParser.php b/extension/Classes/Core/Parser/SimpleParser.php index 4981cd5f9fa0b4bfcfa31fb1460adff5dd6577bb..f92e6128a664ec9853a5d6e03aca3483edca027d 100644 --- a/extension/Classes/Core/Parser/SimpleParser.php +++ b/extension/Classes/Core/Parser/SimpleParser.php @@ -113,7 +113,7 @@ class SimpleParser extends StringTokenizer { * @param $position * @param $expected */ - protected static function raiseUnexpectedDelimiter($delimiter, $position, $expected=null): void { + protected static function raiseUnexpectedDelimiter($delimiter, $position, $expected = null): void { $msg = "An unexpected '$delimiter' at position $position"; if ($expected) { $msg .= " while expecting " . implode(' or ', str_split($expected)); diff --git a/extension/Classes/Core/Parser/StringTokenizer.php b/extension/Classes/Core/Parser/StringTokenizer.php index be3bf3093eb68f6c9165890835f158a7d7911b2f..157cfad3532730b551f482c042b6cb5da608f040 100644 --- a/extension/Classes/Core/Parser/StringTokenizer.php +++ b/extension/Classes/Core/Parser/StringTokenizer.php @@ -46,7 +46,7 @@ class StringTokenizer { * @return int */ public function offset(): int { - return $this->currentOffset-1; + return $this->currentOffset - 1; } /** @@ -60,7 +60,7 @@ class StringTokenizer { * ab\:c d,"e:f" --> , " : " (null) (token pieces: 'ab:c d' '' 'e' 'f' '') * ab\\:cd,' ' --> : , (null) (token pieces: 'ab\' 'cd' ' ') * - * @param string $data the searched string + * @param string $data the searched string * @return \Generator delimiters */ protected function unescapedDelimitersAndQuotes(string $data): \Generator { @@ -86,7 +86,7 @@ class StringTokenizer { } elseif ($tokenData[1] == $offset + 1) { // This delimiter or quote is escaped by the backslash if ($tokenData[0] != '\\') { - $this->tokenBuilder->append(substr($data, $this->currentOffset, $offset-$this->currentOffset)); + $this->tokenBuilder->append(substr($data, $this->currentOffset, $offset - $this->currentOffset)); $this->currentOffset = $tokenData[1]; } $tokenData = next($delimiters); @@ -94,7 +94,7 @@ class StringTokenizer { } } // An unescaped delimiter has been found - $this->tokenBuilder->append(substr($data, $this->currentOffset, $offset-$this->currentOffset)); + $this->tokenBuilder->append(substr($data, $this->currentOffset, $offset - $this->currentOffset)); $this->currentOffset = $offset + 1; yield $delimiter; $tokenData = next($delimiters); @@ -118,7 +118,7 @@ class StringTokenizer { * ab\:c d,"e:f" --> ['ab:c d', ','] ['e f', (null)] * ab\\:cd,' ' --> ['ab\', ':'] ['cd', ','] [' ', (null)] * - * @param string $data the string to search for delimiters + * @param string $data the string to search for delimiters * @return \Generator pairs [token, delimiter] */ public function tokenized(string $data): \Generator { diff --git a/extension/Classes/Core/Parser/Token.php b/extension/Classes/Core/Parser/Token.php index fe9c43a28aea03bb77b4272576f17cbbd29c5f99..5e94c180a685ebd27bc4002de4e45ce353153ad1 100644 --- a/extension/Classes/Core/Parser/Token.php +++ b/extension/Classes/Core/Parser/Token.php @@ -35,7 +35,7 @@ class Token { * that there were no quotes in the token. * @return bool */ - public function empty() : bool { + public function empty(): bool { return $this->value === '' && !$this->isString; } diff --git a/extension/Classes/Core/Parser/TokenBuilder.php b/extension/Classes/Core/Parser/TokenBuilder.php index 92f714187c9b953b6feb5abf1e8e403237769714..b33c71509fc6ffd6fb1a90713a165a5082e22d49 100644 --- a/extension/Classes/Core/Parser/TokenBuilder.php +++ b/extension/Classes/Core/Parser/TokenBuilder.php @@ -48,8 +48,7 @@ class TokenBuilder { * Resets the builder to its initial state * @return void */ - public function reset() - { + public function reset() { $this->pieces = []; $this->length = 0; $this->firstQuoteOffset = -1; @@ -66,8 +65,8 @@ class TokenBuilder { $value = implode('', $this->pieces); if ($this->hasQuotes()) { $value = ltrim(substr($value, 0, $this->firstQuoteOffset)) . - substr($value, $this->firstQuoteOffset, $this->lastQuoteOffset-$this->firstQuoteOffset) . - rtrim(substr($value, $this->lastQuoteOffset)); + substr($value, $this->firstQuoteOffset, $this->lastQuoteOffset - $this->firstQuoteOffset) . + rtrim(substr($value, $this->lastQuoteOffset)); } else { $value = trim($value); } diff --git a/extension/Classes/Core/Store/Config.php b/extension/Classes/Core/Store/Config.php index 9ec3e2d6aefa9c62f6a862ce75e70ba2f884e3c4..8c1156d0e9807e20be8af99d59676ef2867bf505 100644 --- a/extension/Classes/Core/Store/Config.php +++ b/extension/Classes/Core/Store/Config.php @@ -83,7 +83,7 @@ class Config { $absoluteConfigFilePath = $PhpUnitOverloadAbsoluteConfigFilePath === '' ? Path::absoluteConf(CONFIG_QFQ_JSON) : $PhpUnitOverloadAbsoluteConfigFilePath; if (!file_exists($absoluteConfigFilePath)) { HelperFile::createPathRecursive(Path::absoluteConf()); - HelperFile::file_put_contents(Path::absoluteConf(CONFIG_QFQ_JSON_EXAMPLE), json_encode(self::CONFIG_REQUIRED_TEMPLATE, JSON_PRETTY_PRINT)); + HelperFile::file_put_contents(Path::absoluteConf(CONFIG_QFQ_JSON_EXAMPLE), json_encode(self::CONFIG_REQUIRED_TEMPLATE, JSON_PRETTY_PRINT), LOCK_EX); Thrower::userFormException("Please create qfq config file '" . CONFIG_QFQ_JSON . "' in the conf directory which is inside the project directory. Example config file '" . CONFIG_QFQ_JSON_EXAMPLE . "' was created in conf directory.", "Project directory: " . Path::absoluteProject()); } $config = HelperFile::json_decode(HelperFile::file_get_contents($absoluteConfigFilePath)); @@ -202,7 +202,7 @@ class Config { private static function writeConfig(array $config) { $absoluteConf = Path::absoluteConf(); HelperFile::createPathRecursive($absoluteConf); - HelperFile::file_put_contents(Path::join($absoluteConf, CONFIG_QFQ_JSON), json_encode($config, JSON_PRETTY_PRINT)); + HelperFile::file_put_contents(Path::join($absoluteConf, CONFIG_QFQ_JSON), json_encode($config, JSON_PRETTY_PRINT), LOCK_EX); chmod(Path::join($absoluteConf, CONFIG_QFQ_JSON), 0640); } diff --git a/extension/Classes/Core/Store/Store.php b/extension/Classes/Core/Store/Store.php index bcde3238da1791ff7d55c1dfb9c26372dfca6634..37b8cf83c3075747291700d0eedc38d29a1bdb06 100644 --- a/extension/Classes/Core/Store/Store.php +++ b/extension/Classes/Core/Store/Store.php @@ -519,7 +519,9 @@ class Store { if ($storeName === STORE_USER && $key == TYPO3_FE_USER) { $qfqLogPathAbsolute = Path::absoluteQfqLogFile(); $feUserOld = isset($data[$key]) ? $data[$key] : self::getVar($key, STORE_TYPO3 . STORE_EMPTY); - Logger::logMessage(date('Y.m.d H:i:s ') . ": Switch feUser '$feUserOld' to '$value'", $qfqLogPathAbsolute); + if ($feUserOld !== $value) { + Logger::logMessage(date('Y.m.d H:i:s ') . ": Switch feUser '$feUserOld' to '$value'", $qfqLogPathAbsolute); + } } $data[$key] = $value; diff --git a/extension/Resources/Private/Form/cron.json b/extension/Resources/Private/Form/cron.json index cad94e44502adf928c18e8836939dc8d4b332764..1cb4c268c4b8cb3858405450068e36b8815d2851 100644 --- a/extension/Resources/Private/Form/cron.json +++ b/extension/Resources/Private/Form/cron.json @@ -316,8 +316,8 @@ "type": "text", "subrecordOption": "", "encode": "none", - "checkType": "all", - "checkPattern": "", + "checkType": "pattern", + "checkPattern": "^(?!\\/fileadmin\\/)(?!.*\\/fileadmin\\/).*", "onChange": "", "ord": 80, "tabindex": 0, @@ -334,7 +334,7 @@ "placeholder": "", "value": "", "sql1": "", - "parameter": "", + "parameter": "data-pattern-error=Beginning slash before fileadmin is not allowed", "parameterLanguageA": "", "parameterLanguageB": "", "parameterLanguageC": "", diff --git a/extension/ext_localconf.php b/extension/ext_localconf.php index 726084a9766662b399afa72ce5d3daed57a56ccf..c8e7f35721ff158ef5ede212dc35f12da20e5c62 100644 --- a/extension/ext_localconf.php +++ b/extension/ext_localconf.php @@ -27,7 +27,7 @@ if ($typo3VersionInteger >= 10000000) { } \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( - 'IMATHUZH.' . 'qfq', + 'qfq', 'Qfq', $controllerAction, $nonCacheableControllerAction, diff --git a/extension/ext_tables.php b/extension/ext_tables.php index ead3b010609e0c76f3d3719d203adbc68d601deb..9e69f1d4992264d8ed1f385594744948060257fe 100644 --- a/extension/ext_tables.php +++ b/extension/ext_tables.php @@ -8,7 +8,7 @@ if (!defined('TYPO3_MODE')) { } \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( - 'IMATHUZH.' . 'qfq', + 'qfq', 'Qfq', 'QFQ Element', 'EXT:qfq/ext_icon.png' diff --git a/javascript/build/copy.js b/javascript/build/copy.js new file mode 100644 index 0000000000000000000000000000000000000000..ee8e9c56826bebec5bef876b10a9fd0ca15022fa --- /dev/null +++ b/javascript/build/copy.js @@ -0,0 +1,183 @@ +const { ncp } = require("ncp") +const fs = require("fs") +const { exec } = require("child_process"); + +ncp.limit = 16 + +const options = { + js: { + clobber: true, //overwrite dir + stopOnErr: true, + filter: /(.+(?<!\..*)|.+\.debug\.js|qfq\..*\.js|.+\.min\.js|.+\.min\.js\.map)$/ + }, + css: { + clobber: true, //overwrite dir + stopOnErr: true, + filter: /(.+(?<!\..*)|.*\.min\.css|qfq.*\.css|.*\.min\.css\.map)$/ + }, + font: { + clobber: true, //overwrite dir + stopOnErr: true, + filter: /(.+(?<!\..*)|.*\.ttf|.*\.svg|.*\.woff|.*\.woff2)$/ + } +} + +const target = { + js: "extension/Resources/Public/JavaScript/", + css: "extension/Resources/Public/Css/", + font: "extension/Resources/Public/fonts" +} + +const target_dev = { + js: "js/", + css: "css/" +} + +const todos = [ + { + name: "bootstrap", + js: "node_modules/bootstrap/dist/js/", + css: "node_modules/bootstrap/dist/css/", + font: "node_modules/bootstrap/dist/fonts/" + },{ + name: "jquery", + js: "node_modules/jquery/dist/" + },{ + name: "tablesorter", + custom: [ + { + from: "node_modules/tablesorter/dist/js/jquery.tablesorter.combined.min.js", + to: target.js + },{ + from: "node_modules/tablesorter/dist/js/extras/jquery.tablesorter.pager.min.js", + to: target.js + },{ + from: "node_modules/tablesorter/dist/js/widgets/widget-columnSelector.min.js", + to: target.js + },{ + from: "node_modules/tablesorter/dist/js/widgets/widget-output.min.js", + to: target.js + } + ] + },{ + name: "datetimepicker", + js: "javascript/src/Plugins/bootstrap-datetimepicker.min.js", + css: "javascript/src/Plugins/" + },{ + name: "chart-js", + js: "node_modules/chart.js/dist/" + },{ + name: "qfq", + js: "javascript/build/dist/", + css: "less/dist/" + },{ + name: "tinymce", + js: 'node_modules/tinymce/', + custom: [ + { + from: "node_modules/tinymce/skins", + to: target.js + },{ + from: "node_modules/tinymce/plugins", + to: target.js + } + ] + },{ + name: "qfq plugins", + js: "javascript/src/Plugins/", + css: "javascript/src/Plugins/" + },{ + name: "fontAwesome", + css: "node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css", + custom: [ + { + from: "node_modules/@fortawesome/fontawesome-free/css/all.min.css", + to: target.css + "font-awesome.min.css" + },{ + from: "node_modules/@fortawesome/fontawesome-free/webfonts", + to: "extension/Resources/Public/" + } + ] + },{ + name: "fontPassword", + font: "resources/custom_fonts/" + },{ + name: "typeAhead", + js: "node_modules/corejs-typeahead/dist/" + },{ + name: "codemirror", + css: "node_modules/codemirror/lib/", + custom: [ + { + from: "node_modules/codemirror/mode/", + to: target.js + "code-mirror-mode/" + }, + { + from: "node_modules/codemirror/theme/monokai.css", + to: target.css + "theme/" + }, + { + from: "node_modules/codemirror/lib/codemirror.css", + to: target.css + }, + { + from: "node_modules/codemirror/lib/codemirror.js", + to: target.js + } + ] + },{ + name: "EventEmitter", + js: "node_modules/wolfy87-eventemitter/" + },{ + name: "fullcalendar", + js: "node_modules/fullcalendar/dist/", + css: "node_modules/fullcalendar/dist/" + },{ + name: "moment", + js: "node_modules/moment/min/", + },{ + name: "jqwidgets", + custom: [ + { + from: "node_modules/jqwidgets-framework/jqwidgets/jqx-all.js", + to: target.js + },{ + from: "node_modules/jqwidgets-framework/jqwidgets/globalization/globalize.js", + to: target.js + },{ + from: "node_modules/jqwidgets-framework/jqwidgets/styles/jqx.base.css", + to: target.css + },{ + from: "node_modules/jqwidgets-framework/jqwidgets/styles/jqx.bootstrap.css", + to: target.css + } + ] + } +] + +const types = ["js", "css", "font"] + +console.log("Async copying files:") +for (const todo of todos) { + for(const type of types) { + if(todo.hasOwnProperty(type)) { + ncp(todo[type], target[type], options[type], (err) => printProgress(err, todo.name, type)); + } + } + if(todo.hasOwnProperty("custom")) { + for (const job of todo.custom) { + if (!fs.existsSync(job.to.substring(0, job.to.lastIndexOf("/")))) { + fs.mkdirSync(job.to.substring(0, job.to.lastIndexOf("/"))); + } + exec('cp -r "' + job.from + '" "' + job.to + '"', (error, stdout, stderr) => printProgress(error, todo.name, "custom")) + //ncp(job.from, job.to, options.custom, (err) => printProgress(err, todo.name, "custom")) + } + } +} + +function printProgress(err, name, type) { + if (err) { + return console.error(err); + } + console.log(' * copied ' + type + ' ' + name); +} \ No newline at end of file diff --git a/javascript/build/terser.js b/javascript/build/terser.js new file mode 100644 index 0000000000000000000000000000000000000000..109f4fccbaeb75a99c70219314604990a2cabd1f --- /dev/null +++ b/javascript/build/terser.js @@ -0,0 +1,60 @@ +const { minify } = require("terser"); +const fs = require('fs'); + +const jsPath = "javascript/build/dist/" +const extPath = "extension/Resources/Public/JavaScript/" +const cssPath = "extension/Resources/Public/Css/" + +const todos = [ + { + name: "qfq", + input: "javascript/build/dist/qfq.debug.js", + output: jsPath + "qfq.min.js" + },{ + name: "qfqFabric", + input: "javascript/src/Plugins/qfq.fabric.js", + output: jsPath + "qfq.fabric.min.js" + },{ + name: "qfqValidator", + input: "javascript/src/Plugins/validator.js", + output: jsPath + "validator.min.js" + },{ + name: "codemirror", + input: "node_modules/codemirror/lib/codemirror.js", + output: extPath + "codemirror.min.js" + },{ + name: "codemirror sql", + input: "node_modules/codemirror/lib/codemirror.js", + output: extPath + "code-mirror-mode/sql/sql.min.js", + mkdir: extPath + "code-mirror-mode/sql" + } +] + +const defaultOptions = { + compress: { + defaults: false, + ecma: 2015 + } +}; + +async function minifySource(input, output, options) { + let sourceCode = fs.readFileSync(input, 'utf8'); + minify(sourceCode, options) + .then( (res) => callWriteFile(output, res)) +} + +function callWriteFile(output, sourceCode) { + //console.log("Source Code", sourceCode) + fs.writeFileSync(output, sourceCode.code) +} + +for (const todo of todos) { + console.log("minifying " + todo.name) + let options = defaultOptions + if(todo.hasOwnProperty("options")) options = todo.options + if(todo.hasOwnProperty("mkdir")) { + fs.mkdirSync(todo.mkdir, { recursive: true }) + } + minifySource(todo.input, todo.output, options) +} + diff --git a/javascript/src/Element/FormGroup.js b/javascript/src/Core/FormGroup.js similarity index 100% rename from javascript/src/Element/FormGroup.js rename to javascript/src/Core/FormGroup.js diff --git a/javascript/src/QfqEvents.js b/javascript/src/Core/QfqEvents.js similarity index 100% rename from javascript/src/QfqEvents.js rename to javascript/src/Core/QfqEvents.js diff --git a/javascript/src/Main.js b/javascript/src/Main.js index 5fef921a534b7aefe762c2e82103402ad0e8bdce..3a8e3bb3594558e81b4758bec659ebab3493331a 100644 --- a/javascript/src/Main.js +++ b/javascript/src/Main.js @@ -31,7 +31,6 @@ $(document).ready( function () { var collection = document.getElementsByClassName("qfq-form"); - console.log(collection); var qfqPages = []; for (const form of collection) { const page = new n.QfqPage(form.dataset); diff --git a/javascript/src/Plugins/bootstrap-datetimepicker.min.css b/javascript/src/Plugins/bootstrap-datetimepicker.min.css new file mode 100644 index 0000000000000000000000000000000000000000..365654ba572673b19b7e707635120e02de98f935 --- /dev/null +++ b/javascript/src/Plugins/bootstrap-datetimepicker.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap Datetime Picker v4.17.49 + * Copyright 2015-2020 Jonathan Peterson + * Licensed under MIT (https://github.com/Eonasdan/bootstrap-datetimepicker/blob/master/LICENSE) + */.bootstrap-datetimepicker-widget{list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{display:block;margin:2px 0;padding:4px;width:19em}@media (min-width:768px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:992px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:1200px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:before,.bootstrap-datetimepicker-widget.dropdown-menu:after{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid white;top:-6px;left:8px}.bootstrap-datetimepicker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid white;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:bold;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action="today"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget table th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#eee}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#777}.bootstrap-datetimepicker-widget table td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget table td.old,.bootstrap-datetimepicker-widget table td.new{color:#777}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-datetimepicker-widget table td.today:before{content:'';display:inline-block;border:solid transparent;border-width:0 0 7px 7px;border-bottom-color:#337ab7;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget table td span:hover{background:#eee}.bootstrap-datetimepicker-widget table td span.active{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td span.old{color:#777}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.bootstrap-datetimepicker-widget.wider{width:21em}.bootstrap-datetimepicker-widget .datepicker-decades .decade{line-height:1.8em !important}.input-group.date .input-group-addon{cursor:pointer}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0} \ No newline at end of file diff --git a/javascript/src/Plugins/bootstrap-datetimepicker.min.js b/javascript/src/Plugins/bootstrap-datetimepicker.min.js new file mode 100644 index 0000000000000000000000000000000000000000..884a8f879923ac55f9046afc164b592c41dfae46 --- /dev/null +++ b/javascript/src/Plugins/bootstrap-datetimepicker.min.js @@ -0,0 +1 @@ +!function(e){"use strict";if("function"==typeof define&&define.amd)define(["jquery","moment"],e);else if("object"==typeof exports)module.exports=e(require("jquery"),require("moment"));else{if("undefined"==typeof jQuery)throw"bootstrap-datetimepicker requires jQuery to be loaded first";if("undefined"==typeof moment)throw"bootstrap-datetimepicker requires Moment.js to be loaded first";e(jQuery,moment)}}(function($,_){"use strict";if(!_)throw new Error("bootstrap-datetimepicker requires Moment.js to be loaded first");function i(i,p){function a(){return void 0!==_.tz&&void 0!==p.timeZone&&null!==p.timeZone&&""!==p.timeZone}function c(e){var t;return t=null==e?_():_.isDate(e)||_.isMoment(e)?_(e):a()?_.tz(e,B,p.useStrict,p.timeZone):_(e,B,p.useStrict),a()&&t.tz(p.timeZone),t}function d(e){if("string"!=typeof e||1<e.length)throw new TypeError("isEnabled expects a single character string parameter");switch(e){case"y":return-1!==q.indexOf("Y");case"M":return-1!==q.indexOf("M");case"d":return-1!==q.toLowerCase().indexOf("d");case"h":case"H":return-1!==q.toLowerCase().indexOf("h");case"m":return-1!==q.indexOf("m");case"s":return-1!==q.indexOf("s");default:return!1}}function l(){return d("h")||d("m")||d("s")}function u(){return d("y")||d("M")||d("d")}function f(){var e,t,a,n=$("<div>").addClass("timepicker-hours").append($("<table>").addClass("table-condensed")),r=$("<div>").addClass("timepicker-minutes").append($("<table>").addClass("table-condensed")),i=$("<div>").addClass("timepicker-seconds").append($("<table>").addClass("table-condensed")),o=[(e=$("<tr>"),t=$("<tr>"),a=$("<tr>"),d("h")&&(e.append($("<td>").append($("<a>").attr({href:"#",tabindex:"-1",title:p.tooltips.incrementHour}).addClass("btn").attr("data-action","incrementHours").append($("<span>").addClass(p.icons.up)))),t.append($("<td>").append($("<span>").addClass("timepicker-hour").attr({"data-time-component":"hours",title:p.tooltips.pickHour}).attr("data-action","showHours"))),a.append($("<td>").append($("<a>").attr({href:"#",tabindex:"-1",title:p.tooltips.decrementHour}).addClass("btn").attr("data-action","decrementHours").append($("<span>").addClass(p.icons.down))))),d("m")&&(d("h")&&(e.append($("<td>").addClass("separator")),t.append($("<td>").addClass("separator").html(":")),a.append($("<td>").addClass("separator"))),e.append($("<td>").append($("<a>").attr({href:"#",tabindex:"-1",title:p.tooltips.incrementMinute}).addClass("btn").attr("data-action","incrementMinutes").append($("<span>").addClass(p.icons.up)))),t.append($("<td>").append($("<span>").addClass("timepicker-minute").attr({"data-time-component":"minutes",title:p.tooltips.pickMinute}).attr("data-action","showMinutes"))),a.append($("<td>").append($("<a>").attr({href:"#",tabindex:"-1",title:p.tooltips.decrementMinute}).addClass("btn").attr("data-action","decrementMinutes").append($("<span>").addClass(p.icons.down))))),d("s")&&(d("m")&&(e.append($("<td>").addClass("separator")),t.append($("<td>").addClass("separator").html(":")),a.append($("<td>").addClass("separator"))),e.append($("<td>").append($("<a>").attr({href:"#",tabindex:"-1",title:p.tooltips.incrementSecond}).addClass("btn").attr("data-action","incrementSeconds").append($("<span>").addClass(p.icons.up)))),t.append($("<td>").append($("<span>").addClass("timepicker-second").attr({"data-time-component":"seconds",title:p.tooltips.pickSecond}).attr("data-action","showSeconds"))),a.append($("<td>").append($("<a>").attr({href:"#",tabindex:"-1",title:p.tooltips.decrementSecond}).addClass("btn").attr("data-action","decrementSeconds").append($("<span>").addClass(p.icons.down))))),Y||(e.append($("<td>").addClass("separator")),t.append($("<td>").append($("<button>").addClass("btn btn-primary").attr({"data-action":"togglePeriod",tabindex:"-1",title:p.tooltips.togglePeriod}))),a.append($("<td>").addClass("separator"))),$("<div>").addClass("timepicker-picker").append($("<table>").addClass("table-condensed").append([e,t,a])))];return d("h")&&o.push(n),d("m")&&o.push(r),d("s")&&o.push(i),o}function t(){var e,t,a,n=$("<div>").addClass("bootstrap-datetimepicker-widget dropdown-menu"),r=$("<div>").addClass("datepicker").append((t=$("<thead>").append($("<tr>").append($("<th>").addClass("prev").attr("data-action","previous").append($("<span>").addClass(p.icons.previous))).append($("<th>").addClass("picker-switch").attr("data-action","pickerSwitch").attr("colspan",p.calendarWeeks?"6":"5")).append($("<th>").addClass("next").attr("data-action","next").append($("<span>").addClass(p.icons.next)))),a=$("<tbody>").append($("<tr>").append($("<td>").attr("colspan",p.calendarWeeks?"8":"7"))),[$("<div>").addClass("datepicker-days").append($("<table>").addClass("table-condensed").append(t).append($("<tbody>"))),$("<div>").addClass("datepicker-months").append($("<table>").addClass("table-condensed").append(t.clone()).append(a.clone())),$("<div>").addClass("datepicker-years").append($("<table>").addClass("table-condensed").append(t.clone()).append(a.clone())),$("<div>").addClass("datepicker-decades").append($("<table>").addClass("table-condensed").append(t.clone()).append(a.clone()))])),i=$("<div>").addClass("timepicker").append(f()),o=$("<ul>").addClass("list-unstyled"),s=$("<li>").addClass("picker-switch"+(p.collapse?" accordion-toggle":"")).append((e=[],p.showTodayButton&&e.push($("<td>").append($("<a>").attr({"data-action":"today",title:p.tooltips.today}).append($("<span>").addClass(p.icons.today)))),!p.sideBySide&&u()&&l()&&e.push($("<td>").append($("<a>").attr({"data-action":"togglePicker",title:p.tooltips.selectTime}).append($("<span>").addClass(p.icons.time)))),p.showClear&&e.push($("<td>").append($("<a>").attr({"data-action":"clear",title:p.tooltips.clear}).append($("<span>").addClass(p.icons.clear)))),p.showClose&&e.push($("<td>").append($("<a>").attr({"data-action":"close",title:p.tooltips.close}).append($("<span>").addClass(p.icons.close)))),$("<table>").addClass("table-condensed").append($("<tbody>").append($("<tr>").append(e)))));return p.inline&&n.removeClass("dropdown-menu"),Y&&n.addClass("usetwentyfour"),d("s")&&!Y&&n.addClass("wider"),p.sideBySide&&u()&&l()?(n.addClass("timepicker-sbs"),"top"===p.toolbarPlacement&&n.append(s),n.append($("<div>").addClass("row").append(r.addClass("col-md-6")).append(i.addClass("col-md-6"))),"bottom"===p.toolbarPlacement&&n.append(s),n):("top"===p.toolbarPlacement&&o.append(s),u()&&o.append($("<li>").addClass(p.collapse&&l()?"collapse in":"").append(r)),"default"===p.toolbarPlacement&&o.append(s),l()&&o.append($("<li>").addClass(p.collapse&&u()?"collapse":"").append(i)),"bottom"===p.toolbarPlacement&&o.append(s),n.append(o))}function n(){var e,t=(z||i).position(),a=(z||i).offset(),n=p.widgetPositioning.vertical,r=p.widgetPositioning.horizontal;if(p.widgetParent)e=p.widgetParent.append(N);else if(i.is("input"))e=i.after(N).parent();else{if(p.inline)return void(e=i.append(N));(e=i).children().first().after(N)}if("auto"===n&&(n=a.top+1.5*N.height()>=$(window).height()+$(window).scrollTop()&&N.height()+i.outerHeight()<a.top?"top":"bottom"),"auto"===r&&(r=e.width()<a.left+N.outerWidth()/2&&a.left+N.outerWidth()>$(window).width()?"right":"left"),"top"===n?N.addClass("top").removeClass("bottom"):N.addClass("bottom").removeClass("top"),"right"===r?N.addClass("pull-right"):N.removeClass("pull-right"),"static"===e.css("position")&&(e=e.parents().filter(function(){return"static"!==$(this).css("position")}).first()),0===e.length)throw new Error("datetimepicker component should be placed within a non-static positioned container");N.css({top:"top"===n?"auto":t.top+i.outerHeight(),bottom:"top"===n?e.outerHeight()-(e===i?0:t.top):"auto",left:"left"===r?e===i?0:t.left:"auto",right:"left"===r?"auto":e.outerWidth()-i.outerWidth()-(e===i?0:t.left)})}function h(e){"dp.change"===e.type&&(e.date&&e.date.isSame(e.oldDate)||!e.date&&!e.oldDate)||i.trigger(e)}function r(e){"y"===e&&(e="YYYY"),h({type:"dp.update",change:e,viewDate:H.clone()})}function o(e){N&&(e&&(j=Math.max(V,Math.min(3,j+e))),N.find(".datepicker > div").hide().filter(".datepicker-"+Z[j].clsName).show())}function m(e,t){var a,n,r,i;if(e.isValid()&&!(p.disabledDates&&"d"===t&&(a=e,!0===p.disabledDates[a.format("YYYY-MM-DD")])||p.enabledDates&&"d"===t&&(n=e,!0!==p.enabledDates[n.format("YYYY-MM-DD")])||p.minDate&&e.isBefore(p.minDate,t)||p.maxDate&&e.isAfter(p.maxDate,t)||p.daysOfWeekDisabled&&"d"===t&&-1!==p.daysOfWeekDisabled.indexOf(e.day())||p.disabledHours&&("h"===t||"m"===t||"s"===t)&&(r=e,!0===p.disabledHours[r.format("H")])||p.enabledHours&&("h"===t||"m"===t||"s"===t)&&(i=e,!0!==p.enabledHours[i.format("H")]))){if(p.disabledTimeIntervals&&("h"===t||"m"===t||"s"===t)){var o=!1;if($.each(p.disabledTimeIntervals,function(){if(e.isBetween(this[0],this[1]))return!(o=!0)}),o)return}return 1}}function s(){var e,t,a,n=N.find(".datepicker-days"),r=n.find("th"),i=[],o=[];if(u()){for(r.eq(0).find("span").attr("title",p.tooltips.prevMonth),r.eq(1).attr("title",p.tooltips.selectMonth),r.eq(2).find("span").attr("title",p.tooltips.nextMonth),n.find(".disabled").removeClass("disabled"),r.eq(1).text(H.format(p.dayViewHeaderFormat)),m(H.clone().subtract(1,"M"),"M")||r.eq(0).addClass("disabled"),m(H.clone().add(1,"M"),"M")||r.eq(2).addClass("disabled"),e=H.clone().startOf("M").startOf("w").startOf("d"),a=0;a<42;a++)0===e.weekday()&&(t=$("<tr>"),p.calendarWeeks&&t.append('<td class="cw">'+e.week()+"</td>"),i.push(t)),o=["day"],e.isBefore(H,"M")&&o.push("old"),e.isAfter(H,"M")&&o.push("new"),e.isSame(E,"d")&&!W&&o.push("active"),m(e,"d")||o.push("disabled"),e.isSame(c(),"d")&&o.push("today"),0!==e.day()&&6!==e.day()||o.push("weekend"),h({type:"dp.classify",date:e,classNames:o}),t.append('<td data-action="selectDay" data-day="'+e.format("L")+'" class="'+o.join(" ")+'">'+e.date()+"</td>"),e.add(1,"d");var s,d,l;n.find("tbody").empty().append(i),s=N.find(".datepicker-months"),d=s.find("th"),l=s.find("tbody").find("span"),d.eq(0).find("span").attr("title",p.tooltips.prevYear),d.eq(1).attr("title",p.tooltips.selectYear),d.eq(2).find("span").attr("title",p.tooltips.nextYear),s.find(".disabled").removeClass("disabled"),m(H.clone().subtract(1,"y"),"y")||d.eq(0).addClass("disabled"),d.eq(1).text(H.year()),m(H.clone().add(1,"y"),"y")||d.eq(2).addClass("disabled"),l.removeClass("active"),E.isSame(H,"y")&&!W&&l.eq(E.month()).addClass("active"),l.each(function(e){m(H.clone().month(e),"M")||$(this).addClass("disabled")}),function(){var e=N.find(".datepicker-years"),t=e.find("th"),a=H.clone().subtract(5,"y"),n=H.clone().add(6,"y"),r="";for(t.eq(0).find("span").attr("title",p.tooltips.prevDecade),t.eq(1).attr("title",p.tooltips.selectDecade),t.eq(2).find("span").attr("title",p.tooltips.nextDecade),e.find(".disabled").removeClass("disabled"),p.minDate&&p.minDate.isAfter(a,"y")&&t.eq(0).addClass("disabled"),t.eq(1).text(a.year()+"-"+n.year()),p.maxDate&&p.maxDate.isBefore(n,"y")&&t.eq(2).addClass("disabled");!a.isAfter(n,"y");)r+='<span data-action="selectYear" class="year'+(a.isSame(E,"y")&&!W?" active":"")+(m(a,"y")?"":" disabled")+'">'+a.year()+"</span>",a.add(1,"y");e.find("td").html(r)}(),function(){var e,t=N.find(".datepicker-decades"),a=t.find("th"),n=_({y:H.year()-H.year()%100-1}),r=n.clone().add(100,"y"),i=n.clone(),o=!1,s=!1,d="";for(a.eq(0).find("span").attr("title",p.tooltips.prevCentury),a.eq(2).find("span").attr("title",p.tooltips.nextCentury),t.find(".disabled").removeClass("disabled"),(n.isSame(_({y:1900}))||p.minDate&&p.minDate.isAfter(n,"y"))&&a.eq(0).addClass("disabled"),a.eq(1).text(n.year()+"-"+r.year()),(n.isSame(_({y:2e3}))||p.maxDate&&p.maxDate.isBefore(r,"y"))&&a.eq(2).addClass("disabled");!n.isAfter(r,"y");)e=n.year()+12,o=p.minDate&&p.minDate.isAfter(n,"y")&&p.minDate.year()<=e,s=p.maxDate&&p.maxDate.isAfter(n,"y")&&p.maxDate.year()<=e,d+='<span data-action="selectDecade" class="decade'+(E.isAfter(n)&&E.year()<=e?" active":"")+(m(n,"y")||o||s?"":" disabled")+'" data-selection="'+(n.year()+6)+'">'+(n.year()+1)+" - "+(n.year()+12)+"</span>",n.add(12,"y");d+="<span></span><span></span><span></span>",t.find("td").html(d),a.eq(1).text(i.year()+1+"-"+n.year())}()}}function e(){var e,t,a=N.find(".timepicker span[data-time-component]");Y||(e=N.find(".timepicker [data-action=togglePeriod]"),t=E.clone().add(12<=E.hours()?-12:12,"h"),e.text(E.format("A")),m(t,"h")?e.removeClass("disabled"):e.addClass("disabled")),a.filter("[data-time-component=hours]").text(E.format(Y?"HH":"hh")),a.filter("[data-time-component=minutes]").text(E.format("mm")),a.filter("[data-time-component=seconds]").text(E.format("ss")),function(){var e=N.find(".timepicker-hours table"),t=H.clone().startOf("d"),a=[],n=$("<tr>");for(11<H.hour()&&!Y&&t.hour(12);t.isSame(H,"d")&&(Y||H.hour()<12&&t.hour()<12||11<H.hour());)t.hour()%4==0&&(n=$("<tr>"),a.push(n)),n.append('<td data-action="selectHour" class="hour'+(m(t,"h")?"":" disabled")+'">'+t.format(Y?"HH":"hh")+"</td>"),t.add(1,"h");e.empty().append(a)}(),function(){for(var e=N.find(".timepicker-minutes table"),t=H.clone().startOf("h"),a=[],n=$("<tr>"),r=1===p.stepping?5:p.stepping;H.isSame(t,"h");)t.minute()%(4*r)==0&&(n=$("<tr>"),a.push(n)),n.append('<td data-action="selectMinute" class="minute'+(m(t,"m")?"":" disabled")+'">'+t.format("mm")+"</td>"),t.add(r,"m");e.empty().append(a)}(),function(){for(var e=N.find(".timepicker-seconds table"),t=H.clone().startOf("m"),a=[],n=$("<tr>");H.isSame(t,"m");)t.second()%20==0&&(n=$("<tr>"),a.push(n)),n.append('<td data-action="selectSecond" class="second'+(m(t,"s")?"":" disabled")+'">'+t.format("ss")+"</td>"),t.add(5,"s");e.empty().append(a)}()}function y(){N&&(s(),e())}function g(e){var t=W?null:E;if(!e)return W=!0,I.val(""),i.data("date",""),h({type:"dp.change",date:!1,oldDate:t}),void y();if(e=e.clone().locale(p.locale),a()&&e.tz(p.timeZone),1!==p.stepping)for(e.minutes(Math.round(e.minutes()/p.stepping)*p.stepping).seconds(0);p.minDate&&e.isBefore(p.minDate);)e.add(p.stepping,"minutes");m(e)?(H=(E=e).clone(),I.val(E.format(q)),i.data("date",E.format(q)),W=!1,y(),h({type:"dp.change",date:E.clone(),oldDate:t})):(p.keepInvalid?h({type:"dp.change",date:e,oldDate:t}):I.val(W?"":E.format(q)),h({type:"dp.error",date:e,oldDate:t}))}function b(){var t=!1;return N?(N.find(".collapse").each(function(){var e=$(this).data("collapse");return!e||!e.transitioning||!(t=!0)}),t||(z&&z.hasClass("btn")&&z.toggleClass("active"),N.hide(),$(window).off("resize",n),N.off("click","[data-action]"),N.off("mousedown",!1),N.remove(),N=!1,h({type:"dp.hide",date:E.clone()}),I.blur(),H=E.clone()),L):L}function w(){g(null)}function v(e){return void 0===p.parseInputDate?(!_.isMoment(e)||e instanceof Date)&&(e=c(e)):e=p.parseInputDate(e),e}function k(e){return $(e.currentTarget).is(".disabled")||X[$(e.currentTarget).data("action")].apply(L,arguments),!1}function D(){var e;return I.prop("disabled")||!p.ignoreReadonly&&I.prop("readonly")||N||(void 0!==I.val()&&0!==I.val().trim().length?g(v(I.val().trim())):W&&p.useCurrent&&(p.inline||I.is("input")&&0===I.val().trim().length)&&(e=c(),"string"==typeof p.useCurrent&&(e={year:function(e){return e.month(0).date(1).hours(0).seconds(0).minutes(0)},month:function(e){return e.date(1).hours(0).seconds(0).minutes(0)},day:function(e){return e.hours(0).seconds(0).minutes(0)},hour:function(e){return e.seconds(0).minutes(0)},minute:function(e){return e.seconds(0)}}[p.useCurrent](e)),g(e)),N=t(),function(){var e=$("<tr>"),t=H.clone().startOf("w").startOf("d");for(!0===p.calendarWeeks&&e.append($("<th>").addClass("cw").text("#"));t.isBefore(H.clone().endOf("w"));)e.append($("<th>").addClass("dow").text(t.format("dd"))),t.add(1,"d");N.find(".datepicker-days thead").append(e)}(),function(){for(var e=[],t=H.clone().startOf("y").startOf("d");t.isSame(H,"y");)e.push($("<span>").attr("data-action","selectMonth").addClass("month").text(t.format("MMM"))),t.add(1,"M");N.find(".datepicker-months td").empty().append(e)}(),N.find(".timepicker-hours").hide(),N.find(".timepicker-minutes").hide(),N.find(".timepicker-seconds").hide(),y(),o(),$(window).on("resize",n),N.on("click","[data-action]",k),N.on("mousedown",!1),z&&z.hasClass("btn")&&z.toggleClass("active"),n(),N.show(),p.focusOnShow&&!I.is(":focus")&&I.focus(),h({type:"dp.show"})),L}function C(){return(N?b:D)()}function x(e){var t,a,n,r,i=null,o=[],s={},d=e.which;for(t in K[d]="p",K)K.hasOwnProperty(t)&&"p"===K[t]&&(o.push(t),parseInt(t,10)!==d&&(s[t]=!0));for(t in p.keyBinds)if(p.keyBinds.hasOwnProperty(t)&&"function"==typeof p.keyBinds[t]&&(n=t.split(" ")).length===o.length&&J[d]===n[n.length-1]){for(r=!0,a=n.length-2;0<=a;a--)if(!(J[n[a]]in s)){r=!1;break}if(r){i=p.keyBinds[t];break}}i&&(i.call(L,N),e.stopPropagation(),e.preventDefault())}function T(e){K[e.which]="r",e.stopPropagation(),e.preventDefault()}function M(e){var t=$(e.target).val().trim(),a=t?v(t):null;return g(a),e.stopImmediatePropagation(),!1}function S(e){var t={};return $.each(e,function(){var e=v(this);e.isValid()&&(t[e.format("YYYY-MM-DD")]=!0)}),!!Object.keys(t).length&&t}function O(e){var t={};return $.each(e,function(){t[this]=!0}),!!Object.keys(t).length&&t}function P(){var e=p.format||"L LT";q=e.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,function(e){return(E.localeData().longDateFormat(e)||e).replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,function(e){return E.localeData().longDateFormat(e)||e})}),(B=p.extraFormats?p.extraFormats.slice():[]).indexOf(e)<0&&B.indexOf(q)<0&&B.push(q),Y=q.toLowerCase().indexOf("a")<1&&q.replace(/\[.*?\]/g,"").indexOf("h")<1,d("y")&&(V=2),d("M")&&(V=1),d("d")&&(V=0),j=Math.max(V,j),W||g(E)}var E,H,I,Y,q,B,j,A,F,L={},W=!0,z=!1,N=!1,V=0,Z=[{clsName:"days",navFnc:"M",navStep:1},{clsName:"months",navFnc:"y",navStep:1},{clsName:"years",navFnc:"y",navStep:10},{clsName:"decades",navFnc:"y",navStep:100}],R=["days","months","years","decades"],Q=["top","bottom","auto"],U=["left","right","auto"],G=["default","top","bottom"],J={up:38,38:"up",down:40,40:"down",left:37,37:"left",right:39,39:"right",tab:9,9:"tab",escape:27,27:"escape",enter:13,13:"enter",pageUp:33,33:"pageUp",pageDown:34,34:"pageDown",shift:16,16:"shift",control:17,17:"control",space:32,32:"space",t:84,84:"t",delete:46,46:"delete"},K={},X={next:function(){var e=Z[j].navFnc;H.add(Z[j].navStep,e),s(),r(e)},previous:function(){var e=Z[j].navFnc;H.subtract(Z[j].navStep,e),s(),r(e)},pickerSwitch:function(){o(1)},selectMonth:function(e){var t=$(e.target).closest("tbody").find("span").index($(e.target));H.month(t),j===V?(g(E.clone().year(H.year()).month(H.month())),p.inline||b()):(o(-1),s()),r("M")},selectYear:function(e){var t=parseInt($(e.target).text(),10)||0;H.year(t),j===V?(g(E.clone().year(H.year())),p.inline||b()):(o(-1),s()),r("YYYY")},selectDecade:function(e){var t=parseInt($(e.target).data("selection"),10)||0;H.year(t),j===V?(g(E.clone().year(H.year())),p.inline||b()):(o(-1),s()),r("YYYY")},selectDay:function(e){var t=H.clone();$(e.target).is(".old")&&t.subtract(1,"M"),$(e.target).is(".new")&&t.add(1,"M"),g(t.date(parseInt($(e.target).text(),10))),l()||p.keepOpen||p.inline||b()},incrementHours:function(){var e=E.clone().add(1,"h");m(e,"h")&&g(e)},incrementMinutes:function(){var e=E.clone().add(p.stepping,"m");m(e,"m")&&g(e)},incrementSeconds:function(){var e=E.clone().add(1,"s");m(e,"s")&&g(e)},decrementHours:function(){var e=E.clone().subtract(1,"h");m(e,"h")&&g(e)},decrementMinutes:function(){var e=E.clone().subtract(p.stepping,"m");m(e,"m")&&g(e)},decrementSeconds:function(){var e=E.clone().subtract(1,"s");m(e,"s")&&g(e)},togglePeriod:function(){g(E.clone().add(12<=E.hours()?-12:12,"h"))},togglePicker:function(e){var t,a=$(e.target),n=a.closest("ul"),r=n.find(".in"),i=n.find(".collapse:not(.in)");if(r&&r.length){if((t=r.data("collapse"))&&t.transitioning)return;r.collapse?(r.collapse("hide"),i.collapse("show")):(r.removeClass("in"),i.addClass("in")),a.is("span")?a.toggleClass(p.icons.time+" "+p.icons.date):a.find("span").toggleClass(p.icons.time+" "+p.icons.date)}},showPicker:function(){N.find(".timepicker > div:not(.timepicker-picker)").hide(),N.find(".timepicker .timepicker-picker").show()},showHours:function(){N.find(".timepicker .timepicker-picker").hide(),N.find(".timepicker .timepicker-hours").show()},showMinutes:function(){N.find(".timepicker .timepicker-picker").hide(),N.find(".timepicker .timepicker-minutes").show()},showSeconds:function(){N.find(".timepicker .timepicker-picker").hide(),N.find(".timepicker .timepicker-seconds").show()},selectHour:function(e){var t=parseInt($(e.target).text(),10);Y||(12<=E.hours()?12!==t&&(t+=12):12===t&&(t=0)),g(E.clone().hours(t)),X.showPicker.call(L)},selectMinute:function(e){g(E.clone().minutes(parseInt($(e.target).text(),10))),X.showPicker.call(L)},selectSecond:function(e){g(E.clone().seconds(parseInt($(e.target).text(),10))),X.showPicker.call(L)},clear:w,today:function(){var e=c();m(e,"d")&&g(e)},close:b};if(L.destroy=function(){b(),I.off({change:M,blur:blur,keydown:x,keyup:T,focus:p.allowInputToggle?b:""}),i.is("input")?I.off({focus:D}):z&&(z.off("click",C),z.off("mousedown",!1)),i.removeData("DateTimePicker"),i.removeData("date")},L.toggle=C,L.show=D,L.hide=b,L.disable=function(){return b(),z&&z.hasClass("btn")&&z.addClass("disabled"),I.prop("disabled",!0),L},L.enable=function(){return z&&z.hasClass("btn")&&z.removeClass("disabled"),I.prop("disabled",!1),L},L.ignoreReadonly=function(e){if(0===arguments.length)return p.ignoreReadonly;if("boolean"!=typeof e)throw new TypeError("ignoreReadonly () expects a boolean parameter");return p.ignoreReadonly=e,L},L.options=function(e){if(0===arguments.length)return $.extend(!0,{},p);if(!(e instanceof Object))throw new TypeError("options() options parameter should be an object");return $.extend(!0,p,e),$.each(p,function(e,t){if(void 0===L[e])throw new TypeError("option "+e+" is not recognized!");L[e](t)}),L},L.date=function(e){if(0===arguments.length)return W?null:E.clone();if(!(null===e||"string"==typeof e||_.isMoment(e)||e instanceof Date))throw new TypeError("date() parameter must be one of [null, string, moment or Date]");return g(null===e?null:v(e)),L},L.format=function(e){if(0===arguments.length)return p.format;if("string"!=typeof e&&("boolean"!=typeof e||!1!==e))throw new TypeError("format() expects a string or boolean:false parameter "+e);return p.format=e,q&&P(),L},L.timeZone=function(e){if(0===arguments.length)return p.timeZone;if("string"!=typeof e)throw new TypeError("newZone() expects a string parameter");return p.timeZone=e,L},L.dayViewHeaderFormat=function(e){if(0===arguments.length)return p.dayViewHeaderFormat;if("string"!=typeof e)throw new TypeError("dayViewHeaderFormat() expects a string parameter");return p.dayViewHeaderFormat=e,L},L.extraFormats=function(e){if(0===arguments.length)return p.extraFormats;if(!1!==e&&!(e instanceof Array))throw new TypeError("extraFormats() expects an array or false parameter");return p.extraFormats=e,B&&P(),L},L.disabledDates=function(e){if(0===arguments.length)return p.disabledDates?$.extend({},p.disabledDates):p.disabledDates;if(!e)return p.disabledDates=!1,y(),L;if(!(e instanceof Array))throw new TypeError("disabledDates() expects an array parameter");return p.disabledDates=S(e),p.enabledDates=!1,y(),L},L.enabledDates=function(e){if(0===arguments.length)return p.enabledDates?$.extend({},p.enabledDates):p.enabledDates;if(!e)return p.enabledDates=!1,y(),L;if(!(e instanceof Array))throw new TypeError("enabledDates() expects an array parameter");return p.enabledDates=S(e),p.disabledDates=!1,y(),L},L.daysOfWeekDisabled=function(e){if(0===arguments.length)return p.daysOfWeekDisabled.splice(0);if("boolean"==typeof e&&!e)return p.daysOfWeekDisabled=!1,y(),L;if(!(e instanceof Array))throw new TypeError("daysOfWeekDisabled() expects an array parameter");if(p.daysOfWeekDisabled=e.reduce(function(e,t){return 6<(t=parseInt(t,10))||t<0||isNaN(t)||-1===e.indexOf(t)&&e.push(t),e},[]).sort(),p.useCurrent&&!p.keepInvalid){for(var t=0;!m(E,"d");){if(E.add(1,"d"),31===t)throw"Tried 31 times to find a valid date";t++}g(E)}return y(),L},L.maxDate=function(e){if(0===arguments.length)return p.maxDate?p.maxDate.clone():p.maxDate;if("boolean"==typeof e&&!1===e)return p.maxDate=!1,y(),L;"string"==typeof e&&("now"!==e&&"moment"!==e||(e=c()));var t=v(e);if(!t.isValid())throw new TypeError("maxDate() Could not parse date parameter: "+e);if(p.minDate&&t.isBefore(p.minDate))throw new TypeError("maxDate() date parameter is before options.minDate: "+t.format(q));return p.maxDate=t,p.useCurrent&&!p.keepInvalid&&E.isAfter(e)&&g(p.maxDate),H.isAfter(t)&&(H=t.clone().subtract(p.stepping,"m")),y(),L},L.minDate=function(e){if(0===arguments.length)return p.minDate?p.minDate.clone():p.minDate;if("boolean"==typeof e&&!1===e)return p.minDate=!1,y(),L;"string"==typeof e&&("now"!==e&&"moment"!==e||(e=c()));var t=v(e);if(!t.isValid())throw new TypeError("minDate() Could not parse date parameter: "+e);if(p.maxDate&&t.isAfter(p.maxDate))throw new TypeError("minDate() date parameter is after options.maxDate: "+t.format(q));return p.minDate=t,p.useCurrent&&!p.keepInvalid&&E.isBefore(e)&&g(p.minDate),H.isBefore(t)&&(H=t.clone().add(p.stepping,"m")),y(),L},L.defaultDate=function(e){if(0===arguments.length)return p.defaultDate?p.defaultDate.clone():p.defaultDate;if(!e)return p.defaultDate=!1,L;"string"==typeof e&&(e="now"===e||"moment"===e?c():c(e));var t=v(e);if(!t.isValid())throw new TypeError("defaultDate() Could not parse date parameter: "+e);if(!m(t))throw new TypeError("defaultDate() date passed is invalid according to component setup validations");return p.defaultDate=t,(p.defaultDate&&p.inline||""===I.val().trim())&&g(p.defaultDate),L},L.locale=function(e){if(0===arguments.length)return p.locale;if(!_.localeData(e))throw new TypeError("locale() locale "+e+" is not loaded from moment locales!");return p.locale=e,E.locale(p.locale),H.locale(p.locale),q&&P(),N&&(b(),D()),L},L.stepping=function(e){return 0===arguments.length?p.stepping:(e=parseInt(e,10),(isNaN(e)||e<1)&&(e=1),p.stepping=e,L)},L.useCurrent=function(e){var t=["year","month","day","hour","minute"];if(0===arguments.length)return p.useCurrent;if("boolean"!=typeof e&&"string"!=typeof e)throw new TypeError("useCurrent() expects a boolean or string parameter");if("string"==typeof e&&-1===t.indexOf(e.toLowerCase()))throw new TypeError("useCurrent() expects a string parameter of "+t.join(", "));return p.useCurrent=e,L},L.collapse=function(e){if(0===arguments.length)return p.collapse;if("boolean"!=typeof e)throw new TypeError("collapse() expects a boolean parameter");return p.collapse===e||(p.collapse=e,N&&(b(),D())),L},L.icons=function(e){if(0===arguments.length)return $.extend({},p.icons);if(!(e instanceof Object))throw new TypeError("icons() expects parameter to be an Object");return $.extend(p.icons,e),N&&(b(),D()),L},L.tooltips=function(e){if(0===arguments.length)return $.extend({},p.tooltips);if(!(e instanceof Object))throw new TypeError("tooltips() expects parameter to be an Object");return $.extend(p.tooltips,e),N&&(b(),D()),L},L.useStrict=function(e){if(0===arguments.length)return p.useStrict;if("boolean"!=typeof e)throw new TypeError("useStrict() expects a boolean parameter");return p.useStrict=e,L},L.sideBySide=function(e){if(0===arguments.length)return p.sideBySide;if("boolean"!=typeof e)throw new TypeError("sideBySide() expects a boolean parameter");return p.sideBySide=e,N&&(b(),D()),L},L.viewMode=function(e){if(0===arguments.length)return p.viewMode;if("string"!=typeof e)throw new TypeError("viewMode() expects a string parameter");if(-1===R.indexOf(e))throw new TypeError("viewMode() parameter must be one of ("+R.join(", ")+") value");return p.viewMode=e,j=Math.max(R.indexOf(e),V),o(),L},L.toolbarPlacement=function(e){if(0===arguments.length)return p.toolbarPlacement;if("string"!=typeof e)throw new TypeError("toolbarPlacement() expects a string parameter");if(-1===G.indexOf(e))throw new TypeError("toolbarPlacement() parameter must be one of ("+G.join(", ")+") value");return p.toolbarPlacement=e,N&&(b(),D()),L},L.widgetPositioning=function(e){if(0===arguments.length)return $.extend({},p.widgetPositioning);if("[object Object]"!=={}.toString.call(e))throw new TypeError("widgetPositioning() expects an object variable");if(e.horizontal){if("string"!=typeof e.horizontal)throw new TypeError("widgetPositioning() horizontal variable must be a string");if(e.horizontal=e.horizontal.toLowerCase(),-1===U.indexOf(e.horizontal))throw new TypeError("widgetPositioning() expects horizontal parameter to be one of ("+U.join(", ")+")");p.widgetPositioning.horizontal=e.horizontal}if(e.vertical){if("string"!=typeof e.vertical)throw new TypeError("widgetPositioning() vertical variable must be a string");if(e.vertical=e.vertical.toLowerCase(),-1===Q.indexOf(e.vertical))throw new TypeError("widgetPositioning() expects vertical parameter to be one of ("+Q.join(", ")+")");p.widgetPositioning.vertical=e.vertical}return y(),L},L.calendarWeeks=function(e){if(0===arguments.length)return p.calendarWeeks;if("boolean"!=typeof e)throw new TypeError("calendarWeeks() expects parameter to be a boolean value");return p.calendarWeeks=e,y(),L},L.showTodayButton=function(e){if(0===arguments.length)return p.showTodayButton;if("boolean"!=typeof e)throw new TypeError("showTodayButton() expects a boolean parameter");return p.showTodayButton=e,N&&(b(),D()),L},L.showClear=function(e){if(0===arguments.length)return p.showClear;if("boolean"!=typeof e)throw new TypeError("showClear() expects a boolean parameter");return p.showClear=e,N&&(b(),D()),L},L.widgetParent=function(e){if(0===arguments.length)return p.widgetParent;if("string"==typeof e&&(e=$(e)),null!==e&&"string"!=typeof e&&!(e instanceof $))throw new TypeError("widgetParent() expects a string or a jQuery object parameter");return p.widgetParent=e,N&&(b(),D()),L},L.keepOpen=function(e){if(0===arguments.length)return p.keepOpen;if("boolean"!=typeof e)throw new TypeError("keepOpen() expects a boolean parameter");return p.keepOpen=e,L},L.focusOnShow=function(e){if(0===arguments.length)return p.focusOnShow;if("boolean"!=typeof e)throw new TypeError("focusOnShow() expects a boolean parameter");return p.focusOnShow=e,L},L.inline=function(e){if(0===arguments.length)return p.inline;if("boolean"!=typeof e)throw new TypeError("inline() expects a boolean parameter");return p.inline=e,L},L.clear=function(){return w(),L},L.keyBinds=function(e){return 0===arguments.length?p.keyBinds:(p.keyBinds=e,L)},L.getMoment=function(e){return c(e)},L.debug=function(e){if("boolean"!=typeof e)throw new TypeError("debug() expects a boolean parameter");return p.debug=e,L},L.allowInputToggle=function(e){if(0===arguments.length)return p.allowInputToggle;if("boolean"!=typeof e)throw new TypeError("allowInputToggle() expects a boolean parameter");return p.allowInputToggle=e,L},L.showClose=function(e){if(0===arguments.length)return p.showClose;if("boolean"!=typeof e)throw new TypeError("showClose() expects a boolean parameter");return p.showClose=e,L},L.keepInvalid=function(e){if(0===arguments.length)return p.keepInvalid;if("boolean"!=typeof e)throw new TypeError("keepInvalid() expects a boolean parameter");return p.keepInvalid=e,L},L.datepickerInput=function(e){if(0===arguments.length)return p.datepickerInput;if("string"!=typeof e)throw new TypeError("datepickerInput() expects a string parameter");return p.datepickerInput=e,L},L.parseInputDate=function(e){if(0===arguments.length)return p.parseInputDate;if("function"!=typeof e)throw new TypeError("parseInputDate() sholud be as function");return p.parseInputDate=e,L},L.disabledTimeIntervals=function(e){if(0===arguments.length)return p.disabledTimeIntervals?$.extend({},p.disabledTimeIntervals):p.disabledTimeIntervals;if(!e)return p.disabledTimeIntervals=!1,y(),L;if(!(e instanceof Array))throw new TypeError("disabledTimeIntervals() expects an array parameter");return p.disabledTimeIntervals=e,y(),L},L.disabledHours=function(e){if(0===arguments.length)return p.disabledHours?$.extend({},p.disabledHours):p.disabledHours;if(!e)return p.disabledHours=!1,y(),L;if(!(e instanceof Array))throw new TypeError("disabledHours() expects an array parameter");if(p.disabledHours=O(e),p.enabledHours=!1,p.useCurrent&&!p.keepInvalid){for(var t=0;!m(E,"h");){if(E.add(1,"h"),24===t)throw"Tried 24 times to find a valid date";t++}g(E)}return y(),L},L.enabledHours=function(e){if(0===arguments.length)return p.enabledHours?$.extend({},p.enabledHours):p.enabledHours;if(!e)return p.enabledHours=!1,y(),L;if(!(e instanceof Array))throw new TypeError("enabledHours() expects an array parameter");if(p.enabledHours=O(e),p.disabledHours=!1,p.useCurrent&&!p.keepInvalid){for(var t=0;!m(E,"h");){if(E.add(1,"h"),24===t)throw"Tried 24 times to find a valid date";t++}g(E)}return y(),L},L.viewDate=function(e){if(0===arguments.length)return H.clone();if(!e)return H=E.clone(),L;if(!("string"==typeof e||_.isMoment(e)||e instanceof Date))throw new TypeError("viewDate() parameter must be one of [string, moment or Date]");return H=v(e),r(),L},i.is("input"))I=i;else if(0===(I=i.find(p.datepickerInput)).length)I=i.find("input");else if(!I.is("input"))throw new Error('CSS class "'+p.datepickerInput+'" cannot be applied to non input element');if(i.hasClass("input-group")&&(z=0===i.find(".datepickerbutton").length?i.find(".input-group-addon"):i.find(".datepickerbutton")),!p.inline&&!I.is("input"))throw new Error("Could not initialize DateTimePicker without an input element");return E=c(),H=E.clone(),$.extend(!0,p,(F={},(A=i.is("input")||p.inline?i.data():i.find("input").data()).dateOptions&&A.dateOptions instanceof Object&&(F=$.extend(!0,F,A.dateOptions)),$.each(p,function(e){var t="date"+e.charAt(0).toUpperCase()+e.slice(1);void 0!==A[t]&&(F[e]=A[t])}),F)),L.options(p),P(),I.on({change:M,blur:p.debug?"":b,keydown:x,keyup:T,focus:p.allowInputToggle?D:""}),i.is("input")?I.on({focus:D}):z&&(z.on("click",C),z.on("mousedown",!1)),I.prop("disabled")&&L.disable(),I.is("input")&&0!==I.val().trim().length?g(v(I.val().trim())):p.defaultDate&&void 0===I.attr("placeholder")&&g(p.defaultDate),p.inline&&D(),L}return $.fn.datetimepicker=function(a){a=a||{};var t,n=Array.prototype.slice.call(arguments,1),r=!0;if("object"==typeof a)return this.each(function(){var e,t=$(this);t.data("DateTimePicker")||(e=$.extend(!0,{},$.fn.datetimepicker.defaults,a),t.data("DateTimePicker",i(t,e)))});if("string"==typeof a)return this.each(function(){var e=$(this).data("DateTimePicker");if(!e)throw new Error('bootstrap-datetimepicker("'+a+'") method was called on an element that is not using DateTimePicker');t=e[a].apply(e,n),r=t===e}),r||-1<$.inArray(a,["destroy","hide","show","toggle"])?this:t;throw new TypeError("Invalid arguments for DateTimePicker: "+a)},$.fn.datetimepicker.defaults={timeZone:"",format:!1,dayViewHeaderFormat:"MMMM YYYY",extraFormats:!1,stepping:1,minDate:!1,maxDate:!1,useCurrent:!0,collapse:!0,locale:_.locale(),defaultDate:!1,disabledDates:!1,enabledDates:!1,icons:{time:"glyphicon glyphicon-time",date:"glyphicon glyphicon-calendar",up:"glyphicon glyphicon-chevron-up",down:"glyphicon glyphicon-chevron-down",previous:"glyphicon glyphicon-chevron-left",next:"glyphicon glyphicon-chevron-right",today:"glyphicon glyphicon-screenshot",clear:"glyphicon glyphicon-trash",close:"glyphicon glyphicon-remove"},tooltips:{today:"Go to today",clear:"Clear selection",close:"Close the picker",selectMonth:"Select Month",prevMonth:"Previous Month",nextMonth:"Next Month",selectYear:"Select Year",prevYear:"Previous Year",nextYear:"Next Year",selectDecade:"Select Decade",prevDecade:"Previous Decade",nextDecade:"Next Decade",prevCentury:"Previous Century",nextCentury:"Next Century",pickHour:"Pick Hour",incrementHour:"Increment Hour",decrementHour:"Decrement Hour",pickMinute:"Pick Minute",incrementMinute:"Increment Minute",decrementMinute:"Decrement Minute",pickSecond:"Pick Second",incrementSecond:"Increment Second",decrementSecond:"Decrement Second",togglePeriod:"Toggle Period",selectTime:"Select Time"},useStrict:!1,sideBySide:!1,daysOfWeekDisabled:!1,calendarWeeks:!1,viewMode:"days",toolbarPlacement:"default",showTodayButton:!1,showClear:!1,showClose:!1,widgetPositioning:{horizontal:"auto",vertical:"auto"},widgetParent:null,ignoreReadonly:!1,keepOpen:!1,focusOnShow:!0,inline:!1,keepInvalid:!1,datepickerInput:".datepickerinput",keyBinds:{up:function(e){if(e){var t=this.date()||this.getMoment();e.find(".datepicker").is(":visible")?this.date(t.clone().subtract(7,"d")):this.date(t.clone().add(this.stepping(),"m"))}},down:function(e){if(e){var t=this.date()||this.getMoment();e.find(".datepicker").is(":visible")?this.date(t.clone().add(7,"d")):this.date(t.clone().subtract(this.stepping(),"m"))}else this.show()},"control up":function(e){if(e){var t=this.date()||this.getMoment();e.find(".datepicker").is(":visible")?this.date(t.clone().subtract(1,"y")):this.date(t.clone().add(1,"h"))}},"control down":function(e){if(e){var t=this.date()||this.getMoment();e.find(".datepicker").is(":visible")?this.date(t.clone().add(1,"y")):this.date(t.clone().subtract(1,"h"))}},left:function(e){if(e){var t=this.date()||this.getMoment();e.find(".datepicker").is(":visible")&&this.date(t.clone().subtract(1,"d"))}},right:function(e){if(e){var t=this.date()||this.getMoment();e.find(".datepicker").is(":visible")&&this.date(t.clone().add(1,"d"))}},pageUp:function(e){if(e){var t=this.date()||this.getMoment();e.find(".datepicker").is(":visible")&&this.date(t.clone().subtract(1,"M"))}},pageDown:function(e){if(e){var t=this.date()||this.getMoment();e.find(".datepicker").is(":visible")&&this.date(t.clone().add(1,"M"))}},enter:function(){this.hide()},escape:function(){this.hide()},"control space":function(e){e&&e.find(".timepicker").is(":visible")&&e.find('.btn[data-action="togglePeriod"]').click()},t:function(){this.date(this.getMoment())},delete:function(){this.clear()}},debug:!1,allowInputToggle:!1,disabledTimeIntervals:!1,disabledHours:!1,enabledHours:!1,viewDate:!1},$.fn.datetimepicker}); \ No newline at end of file diff --git a/javascript/fabric.min.js b/javascript/src/Plugins/fabric.min.js similarity index 100% rename from javascript/fabric.min.js rename to javascript/src/Plugins/fabric.min.js diff --git a/less/qfq-bs.css.less b/less/qfq-bs.css.less index 5a17d11bcf34c187e822e71613738909a409218e..70d4d11d8caf654badfc26c00633984dcef72dc3 100644 --- a/less/qfq-bs.css.less +++ b/less/qfq-bs.css.less @@ -327,7 +327,7 @@ i.@{spinner_class} { background-repeat: repeat-x; border-color: #d30b6f; font-weight: 200; - text-shadow: 0 1px 0 #e72a8; + text-shadow: #e72a89 0 1px 0; color: #333; } diff --git a/package.json b/package.json index a1a8a5676f48fa2458b340a82f41d0cf3c8af4a9..24118f952af7ab968cd75d1077be5be28e6e2444 100644 --- a/package.json +++ b/package.json @@ -8,31 +8,44 @@ "bootstrap-validator": "^0.11.5", "chart.js": "^2.9.4", "codemirror": "^5.65.15", + "concat": "^1.0.3", "corejs-typeahead": "^1.3.1", - "eonasdan-bootstrap-datetimepicker": "^4.17.49", "fullcalendar": "^3.10.2", - "grunt": "^1.6.1", - "grunt-concat-in-order": "^0.2.6", - "grunt-contrib-concat": "^1.0.1", - "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-jasmine": "^1.1.0", - "grunt-contrib-jshint": "^1.1.0", - "grunt-contrib-less": "^1.2.0", - "grunt-contrib-watch": "^1.0.0", - "grunt-terser": "^2.0.0", "jquery": "latest", "jqwidgets-framework": "4.2.1", - "moment": "^2.29.4", + "jshint": "^2.13.6", + "less": "^4.2.0", + "less-plugin-clean-css": "^1.5.1", + "moment": "latest", + "ncp": "^2.0.0", "popper.js": "^1.16.1", "selenium-webdriver": "^4.14.0", "should": "^11.2.1", "tablesorter": "^2.31.3", - "terser": "^5.21.0", + "terser": "latest", "tinymce": "^4.9.11", "wolfy87-eventemitter": "^4.3.0" }, + "jshintConfig": { + "esversion": 6, + "asi": true + }, + "config": { + "js-dir": "extension/Resources/Public/JavaScript/", + "css-dir": "extension/Resources/Public/Css/", + "font-dir": "extension/Resources/Public/fonts/" + }, "scripts": { - "test": "mocha tests/selenium/test*.js" + "test": "mocha tests/selenium/test*.js", + "create-dirs": "mkdir -p js && mkdir -p javascript/build/dist && mkdir -p extension/Resources/Public/JavaScript/ && mkdir -p extension/Resources/Public/Css/ && mkdir -p extension/Resources/Public/fonts/", + "copy": "node javascript/build/copy.js", + "echo": "echo \"$npm_package_config_js_dir\"", + "concat": "concat -o javascript/build/dist/qfq.debug.js javascript/src/Core/QfqEvents.js javascript/src/Core/FormGroup.js javascript/src/*.js javascript/src/Helper/*.js javascript/src/Element/*.js", + "terser": "node javascript/build/terser.js", + "jshint": "jshint javascript/src --exclude javascript/src/Plugins", + "less": "lessc -clean-css less/qfq-bs.css.less less/dist/qfq-bs.css && lessc -clean-css less/qfq-letter.css.less less/dist/qfq-letter.css && lessc -clean-css less/qfq-plain.css.less less/dist/qfq-plain.css && lessc -clean-css less/tablesorter-bootstrap.less less/dist/tablesorter-bootstrap.css", + "prebuild": "npm run jshint && npm run create-dirs", + "build": "npm run concat && npm run less && npm run terser && npm run copy" }, "license": "ISC", "repository": "https://git.math.uzh.ch/typo3/qfq",