From 5c7867deebc19e7739c1618c921aba5479754b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Tue, 6 Jun 2023 20:23:15 +0200 Subject: [PATCH 001/152] fix(ArchiveService): make it working --- includes/commands/ArchiveCommand.php | 6 +- includes/controllers/ArchiveController.php | 4 +- includes/services/ArchiveService.php | 317 ++++++++++-------- .../includes/services/ArchiveServiceTest.php | 81 ++++- 4 files changed, 245 insertions(+), 163 deletions(-) diff --git a/includes/commands/ArchiveCommand.php b/includes/commands/ArchiveCommand.php index f6f8adc92..b3b85ed7b 100644 --- a/includes/commands/ArchiveCommand.php +++ b/includes/commands/ArchiveCommand.php @@ -38,6 +38,8 @@ protected function configure() ->addOption('database-only', 'd', InputOption::VALUE_NONE, 'Save only the database of the YesWiki') ->addOption('files-only', 'f', InputOption::VALUE_NONE, 'Save only the files of the YesWiki') + ->addOption('foldersToInclude', 'i', InputOption::VALUE_REQUIRED, 'Folders to include, path relative to root, coma separated') + ->addOption('foldersToExclude', 'x', InputOption::VALUE_REQUIRED, 'Folders to exclude, path relative to root, coma separated') ->addOption('hideConfigValues', 'a', InputOption::VALUE_REQUIRED, 'Params to anonymize in wakka.config.php, json_encoded') ->addOption('uid', 'u', InputOption::VALUE_REQUIRED, 'uid to retrive input and ouput files') ; @@ -53,6 +55,8 @@ protected function execute(InputInterface $input, OutputInterface $output) return Command::INVALID; } + $foldersToInclude = $this->prepareFileList($input->getOption('foldersToInclude')); + $foldersToExclude = $this->prepareFileList($input->getOption('foldersToExclude')); $rawHideConfigValues = $input->getOption('hideConfigValues'); $hideConfigValues = null; if (!empty($rawHideConfigValues)) { @@ -64,7 +68,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $uid = $input->getOption('uid'); $uid = empty($uid) ? "" : $uid; - $location = $this->archiveService->archive($output, !$databaseOnly, !$filesOnly, $hideConfigValues, $uid); + $location = $this->archiveService->archive($output, !$databaseOnly, !$filesOnly, $foldersToInclude, $foldersToExclude, $hideConfigValues, $uid); return Command::SUCCESS; } diff --git a/includes/controllers/ArchiveController.php b/includes/controllers/ArchiveController.php index 5eb73299e..91036084c 100644 --- a/includes/controllers/ArchiveController.php +++ b/includes/controllers/ArchiveController.php @@ -179,9 +179,9 @@ protected function startArchive( return $this->archiveService->startArchive( $savefiles, $savedatabase, + [], + [], $startAsync ); } - - } diff --git a/includes/services/ArchiveService.php b/includes/services/ArchiveService.php index ea442b1ad..ee402f802 100644 --- a/includes/services/ArchiveService.php +++ b/includes/services/ArchiveService.php @@ -37,7 +37,7 @@ class ArchiveService 'setup', 'styles', 'templates', - 'test', + 'tests', 'themes', 'tools', 'vendor', @@ -62,6 +62,8 @@ class ArchiveService ]; public const PARAMS_KEY_IN_WAKKA = 'archive'; public const KEY_FOR_PRIVATE_FOLDER = 'privatePath'; + public const KEY_FOR_FOLDERS_TO_INCLUDE = 'foldersToInclude'; + public const KEY_FOR_FOLDERS_TO_EXCLUDE = 'foldersToExclude'; public const KEY_FOR_HIDE_CONFIG_VALUES = 'hideConfigValues'; protected const DEFAULT_FOLDER_NAME_IN_TMP = "yeswiki_archive"; public const ARCHIVE_SUFFIX = "_archive"; @@ -103,6 +105,8 @@ public function __construct( * @param string|OutputInterface &$output * @param bool $savefiles * @param bool $savedatabase + * @param array $foldersToInclude + * @param array $foldersToExclude * @param null|array $hideConfigValuesParams * @param string $uid * @throws Exception @@ -111,6 +115,8 @@ public function archive( &$output, bool $savefiles = true, bool $savedatabase = true, + array $foldersToInclude = [], + array $foldersToExclude = [], ?array $hideConfigValuesParams = null, string $uid = "" ) { @@ -142,8 +148,9 @@ public function archive( } $this->writeOutput($output, "=== Checking free space ===", true, $outputFile); + $blacklistedRootFolders = $this->generateListRootFolders('black',$foldersToExclude); try { - $this->assertEnoughtSpace(); + $this->assertEnoughtSpace($blacklistedRootFolders); } catch (Throwable $th) { $this->writeOutput($output, "There is not enough free space.", true, $outputFile); $this->writeOutput($output, "=> {$th->getMessage()}", true, $outputFile); @@ -200,7 +207,7 @@ public function archive( } $this->writeOutput($output, "=== Creating zip archive ===", true, $outputFile); - $this->createZip($location, $output, $sqlContent, $onlyDb, $hideConfigValuesParams, $inputFile, $outputFile); + $this->createZip($location, $foldersToInclude, $blacklistedRootFolders , $output, $sqlContent, $onlyDb, $hideConfigValuesParams, $inputFile, $outputFile); if (!file_exists($location)) { throw new StopArchiveException("Stop archive : not saved !"); } @@ -390,12 +397,16 @@ public function getForcedUpdateToken(): string * * @param bool $savefiles * @param bool $savedatabase + * @param array $foldersToInclude + * @param array $foldersToExclude * @param bool $callAsync * @return string uid */ public function startArchive( bool $savefiles = true, bool $savedatabase = true, + array $foldersToInclude = [], + array $foldersToExclude = [], bool $callAsync = true ): string { $privatePath = $this->getPrivateFolder(); @@ -408,6 +419,14 @@ public function startArchive( if (!$savedatabase) { $args[] = "-f"; } + if (!empty($foldersToInclude)) { + $args[] = "-i"; + $args[] = implode(",", $foldersToInclude); + } + if (!empty($foldersToExclude)) { + $args[] = "-x"; + $args[] = implode(",", $foldersToExclude); + } $args[] = "-u"; $args[] = $uidData['uid']; @@ -424,7 +443,7 @@ public function startArchive( } } else { $output = ""; - $location = $this->archive($output, $savefiles, $savedatabase, null, $uidData['uid']); + $location = $this->archive($output, $savefiles, $savedatabase, $foldersToInclude, $foldersToExclude, null, $uidData['uid']); if (empty($location)) { $this->cleanUID($uidData['uid'], $privatePath); return ''; @@ -601,6 +620,8 @@ protected function checkIfNeedStop(string $inputFile = ""): bool /** * create the zip file * @param string $zipPath + * @param array $foldersToInclude + * @param array $blacklistedRootFolders * @param string|OutputInterface &$output * @param string $sqlContent * @param bool $onlyDb @@ -610,6 +631,8 @@ protected function checkIfNeedStop(string $inputFile = ""): bool */ protected function createZip( string $zipPath, + array $foldersToInclude, + array $blacklistedRootFolders, &$output, string $sqlContent, bool $onlyDb = false, @@ -624,101 +647,113 @@ protected function createZip( $pathToArchive = preg_replace("/(\/|\\\\)$/", "", $pathToArchive); $dirs = [$pathToArchive]; $dirnamePathLen = strlen($pathToArchive); - - $whitelistedRootFolders = array_map(function($folder) use ($pathToArchive) { - return $pathToArchive . "/" . $folder; - }, self::FOLDERS_TO_INCLUDE); + + $whitelistedRootFolders = $this->generateListRootFolders('white',$foldersToInclude); // open file $zip = new ZipArchive(); $resource = $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); - if ($resource !== true) return; - - if (!$onlyDb) { - // add empty cache folder - $zip->addEmptyDir('cache'); - - while (count($dirs)) { - $dir = current($dirs); - $dir = preg_replace("/(?:\/|\\\\|([^\/\\\\]))$/", "$1", $dir); - $baseDirName = preg_replace("/\\\\/", "/", substr($dir, $dirnamePathLen)); - $baseDirName = preg_replace("/^\//", "", $baseDirName); - if (!empty($baseDirName)) { - $this->writeOutput($output, "Adding folder \"$baseDirName\"", true, $outputFile); - $zip->addEmptyDir($baseDirName); - } - $dh = opendir($dir); - while (false !== ($file = readdir($dh))) { - if ($file != '.' && $file != '..') { - $localName = $dir.DIRECTORY_SEPARATOR.$file; - $relativeName = (empty($baseDirName) ? "" : "$baseDirName/").$file; - if (empty($baseDirName) && $file == "wakka.config.php") { - $zip->addFromString($relativeName, $this->getWakkaConfigSanitized($hideConfigValuesParams)); - } elseif (is_file($localName)) { - $zip->addFile($localName, $relativeName); - } elseif (is_dir($localName)) { - if ($this->shouldIncludeFolder($localName, $file, $whitelistedRootFolders)) { - $dirs[] = $dir.DIRECTORY_SEPARATOR.$file; - } - if ($this->checkIfNeedStop($inputFile)) { - $zip->unchangeAll(); - $this->writeOutput($output, "== Closing archive after undoing all changes ==", true, $outputFile); - $zip->close(); - throw new StopArchiveException("Stop archive"); - } + if ($resource !== true) { + return; + } + if (!$onlyDb) { + // add empty cache folder + $zip->addEmptyDir('cache'); + + while (count($dirs)) { + $dir = current($dirs); + $dir = preg_replace("/(?:\/|\\\\|([^\/\\\\]))$/", "$1", $dir); + $baseDirName = preg_replace("/\\\\/", "/", substr($dir, $dirnamePathLen)); + $baseDirName = preg_replace("/^\//", "", $baseDirName); + if (empty($baseDirName) || (!empty($baseDirName) && $this->shouldIncludeFolder($baseDirName, $whitelistedRootFolders,$blacklistedRootFolders))){ + if (!empty($baseDirName)) { + $this->writeOutput($output, "Adding folder \"$baseDirName\"", true, $outputFile); + $zip->addEmptyDir($baseDirName); + } + + $dh = opendir($dir); + while (false !== ($file = readdir($dh))) { + if ($file != '.' && $file != '..') { + $localName = $dir.DIRECTORY_SEPARATOR.$file; + $relativeName = (empty($baseDirName) ? "" : "$baseDirName/").$file; + if (empty($baseDirName) && $file == "wakka.config.php") { + $zip->addFromString($relativeName, $this->getWakkaConfigSanitized($whitelistedRootFolders,$blacklistedRootFolders,$hideConfigValuesParams)); + } elseif (is_file($localName)) { + $zip->addFile($localName, $relativeName); + } elseif (is_dir($localName)) { + if ($this->shouldIncludeFolder($relativeName, $whitelistedRootFolders,$blacklistedRootFolders)) { + $dirs[] = $dir.DIRECTORY_SEPARATOR.$file; + } + if ($this->checkIfNeedStop($inputFile)) { + $zip->unchangeAll(); + $this->writeOutput($output, "== Closing archive after undoing all changes ==", true, $outputFile); + $zip->close(); + throw new StopArchiveException("Stop archive"); } } } - closedir($dh); - - array_shift($dirs); + } } + closedir($dh); + array_shift($dirs); } + } + if (!empty($sqlContent)) { + $this->writeOutput($output, "Adding SQL file", true, $outputFile); + $zip->addEmptyDir(self::PRIVATE_FOLDER_NAME_IN_ZIP); + $zip->addFromString( + self::PRIVATE_FOLDER_NAME_IN_ZIP."/".self::SQL_FILENAME_IN_PRIVATE_FOLDER_IN_ZIP, + $sqlContent + ); + $this->writeOutput($output, "Adding .htaccess file in folder ".self::PRIVATE_FOLDER_NAME_IN_ZIP, true, $outputFile); - if (!empty($sqlContent)) { - $this->writeOutput($output, "Adding SQL file", true, $outputFile); - $zip->addEmptyDir(self::PRIVATE_FOLDER_NAME_IN_ZIP); - $zip->addFromString( - self::PRIVATE_FOLDER_NAME_IN_ZIP."/".self::SQL_FILENAME_IN_PRIVATE_FOLDER_IN_ZIP, - $sqlContent - ); - $this->writeOutput($output, "Adding .htaccess file in folder ".self::PRIVATE_FOLDER_NAME_IN_ZIP, true, $outputFile); - - $zip->addFromString( - self::PRIVATE_FOLDER_NAME_IN_ZIP."/.htaccess", - "DENY FROM ALL\n" - ); - - $zip->addFromString( - self::PRIVATE_FOLDER_NAME_IN_ZIP."/README.md", - self::PRIVATE_FOLDER_README_DEFAULT_CONTENT - ); - } + $zip->addFromString( + self::PRIVATE_FOLDER_NAME_IN_ZIP."/.htaccess", + "DENY FROM ALL\n" + ); - $this->writeOutput($output, "Generating zip file", true, $outputFile); - // register cancel callback if available - if (method_exists($zip, 'registerCancelCallback')) { - $zip->registerCancelCallback(function () use ($inputFile) { - // 0 will continue process - return ($this->checkIfNeedStop($inputFile)) ? -1 : 0; - }); - } - // register progress callback if available - if (method_exists($zip, 'registerProgressCallback')) { - $zip->registerProgressCallback(0.1, function ($r) use (&$output, $outputFile) { - $this->writeOutput($output, "Zip file creation : ".strval(round($r*100, 0))." %", true, $outputFile); - }); - } - $zip->close(); + $zip->addFromString( + self::PRIVATE_FOLDER_NAME_IN_ZIP."/README.md", + self::PRIVATE_FOLDER_README_DEFAULT_CONTENT + ); } + $this->writeOutput($output, "Generating zip file", true, $outputFile); + // register cancel callback if available + if (method_exists($zip, 'registerCancelCallback')) { + $zip->registerCancelCallback(function () use ($inputFile) { + // 0 will continue process + return ($this->checkIfNeedStop($inputFile)) ? -1 : 0; + }); + } + // register progress callback if available + if (method_exists($zip, 'registerProgressCallback')) { + $zip->registerProgressCallback(0.1, function ($r) use (&$output, $outputFile) { + $this->writeOutput($output, "Zip file creation : ".strval(round($r*100, 0))." %", true, $outputFile); + }); + } + $zip->close(); + } - protected function shouldIncludeFolder($path, $folderName, $whitelistedRootFolders) + /** + * test if folder should be included + * @param string $relativeFolderName + * @param array $whitelistedRootFolders + * @param array $blacklistedRootFolders + * @return bool + */ + protected function shouldIncludeFolder( + string $relativeFolderName, + array $whitelistedRootFolders, + array $blacklistedRootFolders + ): bool { - $relativeFolderName = str_replace(getcwd().'/', '', $path); - if (in_array($relativeFolderName, self::FOLDERS_TO_EXCLUDE)) return false; + if (in_array($relativeFolderName, $blacklistedRootFolders) || + in_array(basename($relativeFolderName), $blacklistedRootFolders)){ + return false; + } - return count(array_filter($whitelistedRootFolders, function($folder) use($path) { - return strpos($path, $folder) !== false; + return count(array_filter($whitelistedRootFolders, function($folder) use($relativeFolderName) { + return strpos($relativeFolderName, $folder) === 0; })) > 0; } @@ -741,66 +776,6 @@ private function sanitizeFileList(array $list): array return $outputList; } - /** - * @param array $list - * @param array $ignoreList - * @param string $inputFile - * @return array - */ - private function prepareFileListFromGlob(array $list, array $ignoreList = [], string $inputFile = ""): array - { - $outputList = []; - $onlyChildren = []; - foreach ($list as $filePath) { - foreach (glob($filePath) as $filename) { - $filename = str_replace("\\", "/", $filename); - if (is_dir($filename)) { - $foundChildren = array_filter($ignoreList, function ($path) use ($filename) { - return substr($path, 0, strlen($filename)) == $filename; - }); - if (!empty($foundChildren)) { - foreach ($foundChildren as $path) { - $this->appendChildPathToChildren($onlyChildren, $filename, $path, $inputFile); - } - } - } - if (empty($onlyChildren[$filename]) && !in_array($filename, $outputList) && !in_array($filename, $ignoreList)) { - $outputList[] = $filename; - } - if ($this->checkIfNeedStop($inputFile)) { - throw new StopArchiveException("Stop archive"); - } - } - } - return [$outputList,$onlyChildren]; - } - - /** - * @param &array $onlyChildren - * @param string $dirname - * @param string $path - * @param string $inputFile - * @return array - */ - private function appendChildPathToChildren(array &$onlyChildren, string $dirname, string $path, string $inputFile) - { - if (empty($onlyChildren[$dirname])) { - $onlyChildren[$dirname] = []; - } - $currentPath = $path; - $parentDir = dirname($currentPath); - while ($parentDir != $dirname) { - $this->appendChildPathToChildren($onlyChildren, $parentDir, $currentPath, $inputFile); - $currentPath = $parentDir; - $parentDir = dirname($currentPath); - if ($this->checkIfNeedStop($inputFile)) { - throw new StopArchiveException("Stop archive"); - } - } - if (!in_array($currentPath, $onlyChildren[$dirname])) { - $onlyChildren[$dirname][] = $currentPath; - } - } private function getPrivateFolder(): string { @@ -922,10 +897,12 @@ private function writeOutput(&$output, string $text, bool $newline = true, strin /** * sanitize wakka.config.php before saving it + * @param array $foldersToInclude + * @param array $foldersToExclude * @param null|array $hideConfigValuesParams * @return string */ - private function getWakkaConfigSanitized(?array $hideConfigValuesParams = null): string + private function getWakkaConfigSanitized(array $foldersToInclude, array $foldersToExclude, ?array $hideConfigValuesParams = null): string { // get wakka.config.php content $config = $this->configurationService->getConfiguration('wakka.config.php'); @@ -936,6 +913,12 @@ private function getWakkaConfigSanitized(?array $hideConfigValuesParams = null): } else { $data = $config[self::PARAMS_KEY_IN_WAKKA]; } + if (!empty($foldersToInclude)) { + $data[self::KEY_FOR_FOLDERS_TO_INCLUDE] = $foldersToInclude; + } + if (!empty($foldersToExclude)) { + $data[self::KEY_FOR_FOLDERS_TO_EXCLUDE] = $foldersToExclude; + } if (!is_null($hideConfigValuesParams)) { $data[self::KEY_FOR_HIDE_CONFIG_VALUES] = $hideConfigValuesParams; } elseif (!isset($data[self::KEY_FOR_HIDE_CONFIG_VALUES]) || !is_array($data[self::KEY_FOR_HIDE_CONFIG_VALUES])) { @@ -1052,12 +1035,21 @@ protected function getSQLContent(string $privatePath): string /** * check if there is enought free space before archive (size of files + custom + 300 Mo) + * @param array $blacklistedRootFolders * @throws Exception */ - protected function assertEnoughtSpace() + protected function assertEnoughtSpace(array $blacklistedRootFolders = []) { - $estimateZipSize = $this->folderSize("files"); - $estimateZipSize += $this->folderSize("custom"); + if (empty($blacklistedRootFolders)){ + $blacklistedRootFolders = self::FOLDERS_TO_EXCLUDE; + } + $estimateZipSize = 0; + if (!in_array('files',$blacklistedRootFolders)){ + $estimateZipSize += $this->folderSize("files"); + } + if (!in_array('custom',$blacklistedRootFolders)){ + $estimateZipSize += $this->folderSize("custom"); + } $estimateZipSize += 300 * 1024 * 1024; // 300Mb for the rest of te wiki $freeSpace = disk_free_space(realpath(getcwd())); @@ -1307,4 +1299,33 @@ private function getRunningUIDdata(string $uid, array $info): array return compact(['running','finished','stopped','output']); } + + /** + * generate ---ListedRootFolder from DEFAULT, params and wakka.config + * @param string $type "white"|"black" + * @param array $fromParams + * @return array + * + */ + private function generateListRootFolders(string $type,array $fromParams): array + { + $list = ($type == "white") ? self::FOLDERS_TO_INCLUDE : self::FOLDERS_TO_EXCLUDE; + foreach ($this->sanitizeFileList($fromParams) as $folderName) { + if (!in_array($folderName,$list)){ + $list[] = $folderName; + } + } + // merge `foldersToInclude` or `foldersToExclude` from wakka.config.php + $archiveParams = $this->getArchiveParams(); + $key = ($type == "white") ? self::KEY_FOR_FOLDERS_TO_INCLUDE : self::KEY_FOR_FOLDERS_TO_EXCLUDE; + if (!empty($archiveParams[$key]) && + is_array($archiveParams[$key])) { + foreach ($this->sanitizeFileList($archiveParams[$key]) as $path) { + if (!in_array($path, $list)) { + $list[] = $path; + } + } + } + return $list; + } } diff --git a/tests/includes/services/ArchiveServiceTest.php b/tests/includes/services/ArchiveServiceTest.php index 853c00877..642e65ae0 100644 --- a/tests/includes/services/ArchiveServiceTest.php +++ b/tests/includes/services/ArchiveServiceTest.php @@ -33,6 +33,8 @@ public function testArchiveServiceExisting(): array * @covers ArchiveService::archive * @param bool $savefiles * @param bool $savedatabase + * @param array $foldersToInclude + * @param array $foldersToExclude * @param string $locationSuffix * @param null|int $nbFiles * @param array $filesToFind @@ -42,6 +44,8 @@ public function testArchiveServiceExisting(): array public function testArchive( bool $savefiles, bool $savedatabase, + array $foldersToInclude, + array $foldersToExclude, string $locationSuffix, ?int $nbFiles, array $filesToFind, @@ -52,7 +56,9 @@ public function testArchive( $location = $services['archiveService']->archive( $output, $savefiles, - $savedatabase + $savedatabase, + $foldersToInclude, + $foldersToExclude, ); $data = $this->getDataFromLocation($location, $services['wiki']); $error = $data['error'] ?? ""; @@ -65,19 +71,63 @@ public function testArchive( $this->assertContains($path, $data['files']); } $this->assertCount($nbFiles, $data['files']); - if (!is_null($wakkaContent)) { - $this->assertArrayHasKey('wakkaContent', $data); - $this->checkWakkaContent($wakkaContent, $data['wakkaContent']); - } + } + if (!is_null($wakkaContent)) { + $this->assertArrayHasKey('wakkaContent', $data); + $this->checkWakkaContent($wakkaContent, $data['wakkaContent']); } } public function archiveProvider() { + if (!class_exists(ArchiveService::class,false)){ + include_once 'includes/services/ArchiveService.php'; + } + $defaultFoldersToInclude = constant("\\YesWiki\\Core\\Service\\ArchiveService::FOLDERS_TO_INCLUDE"); + $defaultFoldersToExclude = constant("\\YesWiki\\Core\\Service\\ArchiveService::FOLDERS_TO_EXCLUDE"); return [ + 'archive only root files' => [ + 'savefiles' => true, + 'savedatabase' => false, + 'foldersToInclude' => [], + 'foldersToExclude' => $defaultFoldersToInclude, + 'locationSuffix' => "ARCHIVE_ONLY_FILES_SUFFIX", + 'nbFiles' => -1, + 'filesToFind' => ['wakka.config.php'], + 'wakkaContent' => [ + 'archive' => [ + 'foldersToInclude' => $defaultFoldersToInclude, + 'foldersToExclude' => array_merge($defaultFoldersToExclude,$defaultFoldersToInclude) + ], + ] + ], + 'archive only root files with database' => [ + 'savefiles' => true, + 'savedatabase' => true, + 'foldersToInclude' => [], + 'foldersToExclude' => $defaultFoldersToInclude, + 'locationSuffix' => "ARCHIVE_SUFFIX", + 'nbFiles' => -1, + 'filesToFind' => [ + 'wakka.config.php', + 'private', + 'private/backups', + 'private/backups/.htaccess', + 'private/backups/README.md', + 'private/backups/content.sql', + ], + 'wakkaContent' => [ + 'archive' => [ + 'foldersToInclude' => $defaultFoldersToInclude, + 'foldersToExclude' => array_merge($defaultFoldersToExclude,$defaultFoldersToInclude) + ], + ] + ], 'archive only database' => [ 'savefiles' => false, 'savedatabase' => true, + 'foldersToInclude' => [], + 'foldersToExclude' => [], 'locationSuffix' => "ARCHIVE_ONLY_DATABASE_SUFFIX", 'nbFiles' => 5, 'filesToFind' => [ @@ -88,7 +138,7 @@ public function archiveProvider() 'private/backups/content.sql' ], 'wakkaContent' => null - ] + ], ]; } @@ -192,8 +242,12 @@ private function checkWakkaContent($contentDefinition, $contentToCheck) if (is_array($contentDefinition)) { $this->assertIsArray($contentToCheck); foreach ($contentDefinition as $key => $value) { - $this->assertArrayHasKey($key, $contentToCheck); - $this->checkWakkaContent($contentDefinition[$key], $contentToCheck[$key]); + if (is_integer($key) && is_scalar($value)){ + $this->assertContains($value,$contentToCheck); + } else { + $this->assertArrayHasKey($key, $contentToCheck); + $this->checkWakkaContent($contentDefinition[$key], $contentToCheck[$key]); + } } } elseif (is_scalar($contentDefinition)) { $this->assertEquals($contentDefinition, $contentToCheck); @@ -217,10 +271,12 @@ public function testNotArchiveInParallel( $consoleService = $services['wiki']->services->get(ConsoleService::class); $previousStatus = $params->has('wiki_status') ? $params->get('wiki_status') : null; $this->setWikiStatus($configService, $status); + + $defaultFoldersToInclude = constant("\\YesWiki\\Core\\Service\\ArchiveService::FOLDERS_TO_INCLUDE"); + $results = $consoleService->startConsoleSync("core:archive", [ "-f", - "-x","*,.*", - "-e","wakka.config.php" + "-x",implode(',',$defaultFoldersToInclude), ]); if (empty($previousStatus)) { $this->unsetWikiStatus($configService); @@ -279,10 +335,11 @@ public function testhideConfigValuesParams( $configService = $services['wiki']->services->get(ConfigurationService::class); $consoleService = $services['wiki']->services->get(ConsoleService::class); + $defaultFoldersToInclude = constant("\\YesWiki\\Core\\Service\\ArchiveService::FOLDERS_TO_INCLUDE"); + $consoleParams = [ "-f", - "-x","*,.*", - "-e","wakka.config.php" + "-x",implode(',',$defaultFoldersToInclude), ]; $previoushideConfigValuesParams = $this->getHideConfigValuesParam($configService); From 86fdf2a12650e80120cd4fdeb761af967337f2ad Mon Sep 17 00:00:00 2001 From: Marseault Laurent Date: Thu, 8 Jun 2023 15:19:18 +0200 Subject: [PATCH 002/152] Update aceditor_fr.inc.php fote d'orthographe --- tools/aceditor/lang/aceditor_fr.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/aceditor/lang/aceditor_fr.inc.php b/tools/aceditor/lang/aceditor_fr.inc.php index b016ed5a4..3b0b9440f 100755 --- a/tools/aceditor/lang/aceditor_fr.inc.php +++ b/tools/aceditor/lang/aceditor_fr.inc.php @@ -1,10 +1,10 @@ 'Choisissez un Formulaire', + 'ACTION_BUILDER_CHOOSE_FORM' => 'Choisissez un formulaire', 'ACTION_BUILDER_CHOOSE_TEMPLATE' => 'Sous quelle forme voulez-vous afficher les données?', 'ACTION_BUILDER_CHOOSE_ACTION' => 'Choisissez une action', - 'ACTION_BUILDER_WIKI_CODE_TITLE' => "Code à include dans la page", + 'ACTION_BUILDER_WIKI_CODE_TITLE' => "Code à inclure dans la page", 'ACTION_BUILDER_PARAMETERS' => 'Paramètres', 'ACTION_BUILDER_TEMPLATE_CUSTOM' => 'Template custom', 'ACTION_BUILDER_SEVERAL_FORMS_HINT' => 'Nb. : Il est possible d\'afficher plusieurs bases de données d\'un coup en entrant leur id séparé par une virgule [accéder à la documentation].', From 5269b0008af5429b64ae8cf4bc0b451b85a2aa8c Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sat, 10 Jun 2023 18:32:08 +0200 Subject: [PATCH 003/152] Fixs #1082 rename "root" placeholder to avoid security alerts --- javascripts/documentation.js | 2 +- .../presentation/javascripts/actions-builder-app.js | 4 ++-- .../presentation/javascripts/components/InputPageList.js | 6 +++--- .../presentation/javascripts/components/PreviewAction.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/javascripts/documentation.js b/javascripts/documentation.js index 511f6a566..2bbbbe3cd 100644 --- a/javascripts/documentation.js +++ b/javascripts/documentation.js @@ -73,7 +73,7 @@ window.$docsify = { const height = data.split('preview=')[1] || 200 const codeDom = preDom.querySelector('code') const code = codeDom.textContent - let url = `${baseUrl}root/render` + let url = `${baseUrl}wiki/render` url += url.includes('?') ? '&' : '?' url += `content=${encodeURIComponent(code)}` const preview = document.createElement('div') diff --git a/tools/aceditor/presentation/javascripts/actions-builder-app.js b/tools/aceditor/presentation/javascripts/actions-builder-app.js index 346f2ce68..9f4ab0d5f 100644 --- a/tools/aceditor/presentation/javascripts/actions-builder-app.js +++ b/tools/aceditor/presentation/javascripts/actions-builder-app.js @@ -201,7 +201,7 @@ export const app = { // force watcher without changing value because VueJs will not detect the change // The comparison between changes is done at regular interval, so there will not have detection // of change if the value retrieve its previous value before the end of the interval - this.watchSelectedActionId() + this.watchSelectedActionId() } else { this.selectedActionId = Object.keys(this.actions)[0] } @@ -251,7 +251,7 @@ export const app = { params[`id[${index}]`] = id }) } - $.getJSON(wiki.url('?root/json', params), (data) => { + $.getJSON(wiki.url('?wiki/json', params), (data) => { this.loadingForms = this.loadingForms.filter((e) => !idsToSearch.includes(e)) // keep ? because standart http rewrite waits for CamelCase and 'root' is not if (Array.isArray(data) && data[0] != undefined) { diff --git a/tools/aceditor/presentation/javascripts/components/InputPageList.js b/tools/aceditor/presentation/javascripts/components/InputPageList.js index 05720303f..d52aaf989 100644 --- a/tools/aceditor/presentation/javascripts/components/InputPageList.js +++ b/tools/aceditor/presentation/javascripts/components/InputPageList.js @@ -3,7 +3,7 @@ export default { computed: { pageList() { $.ajax({ - url: wiki.url('?root/json', { demand: 'pages' }), // keep ? because standart http rewrite waits for CamelCase and 'root' is not + url: wiki.url('?wiki/json', { demand: 'pages' }), // keep ? because standart http rewrite waits for CamelCase and 'root' is not async: true, dataType: 'json', type: 'GET', @@ -33,8 +33,8 @@ export default { diff --git a/tools/aceditor/presentation/javascripts/components/PreviewAction.js b/tools/aceditor/presentation/javascripts/components/PreviewAction.js index 80bec6b37..66953d5d9 100644 --- a/tools/aceditor/presentation/javascripts/components/PreviewAction.js +++ b/tools/aceditor/presentation/javascripts/components/PreviewAction.js @@ -3,7 +3,7 @@ export default { computed: { previewIframeUrl() { if (!this.wikiCode) return '' - const result = wiki.url('root/render', { content: this.wikiCode }) + const result = wiki.url('wiki/render', { content: this.wikiCode }) return result } }, From 634ee536035fcea99a1f44bfc78ed7ef98742333 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 004/152] feat(theme): only rely on ThemeManager to get the available themes --- includes/services/ThemeManager.php | 5 ---- .../libs/setwikidefaulttheme.functions.php | 23 +------------------ 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/includes/services/ThemeManager.php b/includes/services/ThemeManager.php index 88683125f..49b83967f 100644 --- a/includes/services/ThemeManager.php +++ b/includes/services/ThemeManager.php @@ -369,11 +369,6 @@ public function getUseFallbackTheme(): bool return $this->useFallbackTheme; } - public function setTemplates(array $templates) - { - $this->templates = $templates; - } - protected function getConfigAsStringOrDefault(string $key, string $default): string { return ($this->params->has($key) && !empty($this->params->get($key)) diff --git a/tools/templates/libs/setwikidefaulttheme.functions.php b/tools/templates/libs/setwikidefaulttheme.functions.php index 242ba3975..cbd8f6297 100644 --- a/tools/templates/libs/setwikidefaulttheme.functions.php +++ b/tools/templates/libs/setwikidefaulttheme.functions.php @@ -4,29 +4,8 @@ function getTemplatesList() { - //on cherche tous les dossiers du repertoire themes et des sous dossier styles - //et squelettes, et on les range dans le tableau $wakkaConfig['templates'] - $repertoire_initial = 'themes'; - $themeManager = $GLOBALS['wiki']->services->get(ThemeManager::class); - if (empty($themeManager->getTemplates())) { - $themeManager->setTemplates(search_template_files($repertoire_initial)); - - //s'il y a un repertoire themes a la racine, on va aussi chercher les templates dedans - if (is_dir('themes')) { - $repertoire_racine = 'themes'; - $themeManager->setTemplates(array_merge( - $themeManager->getTemplates(), - search_template_files($repertoire_racine) - )); - if (is_array($themeManager->getTemplates())) { - $tmp = $themeManager->getTemplates(); - ksort($tmp); - $themeManager->setTemplates($tmp); - } - } - } - // Réorganisation des données avant de les rendre. + $themeManager = $GLOBALS['wiki']->services->get(ThemeManager::class); $themes = array(); foreach ($themeManager->getTemplates() as $templateName => $templateValues) { $themes[$templateName] = array( From da47d829247d826715e575d7e9a0e681b8d81788 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 005/152] feat(theme): use default sort managed by opendir, so margot.css is befor emargot-fun.css --- tools/templates/libs/templates.functions.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/templates/libs/templates.functions.php b/tools/templates/libs/templates.functions.php index 85255d468..faa0cc48f 100755 --- a/tools/templates/libs/templates.functions.php +++ b/tools/templates/libs/templates.functions.php @@ -52,9 +52,6 @@ function search_template_files($directory) } } closedir($dir2); - if (is_array($tab_themes[$file]["style"])) { - ksort($tab_themes[$file]["style"]); - } } $dir3 = opendir($directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'squelettes'); while (false !== ($file3 = readdir($dir3))) { @@ -63,9 +60,6 @@ function search_template_files($directory) } } closedir($dir3); - if (is_array($tab_themes[$file]["squelette"])) { - ksort($tab_themes[$file]["squelette"]); - } $pathToPresets = $directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'presets'; if (is_dir($pathToPresets) && $dir4 = opendir($pathToPresets)) { while (false !== ($file4 = readdir($dir4))) { From 9f98e02308528b6008258bbe3b1f50ae9a86d608 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 006/152] feat(theme): use $themes variable instead of $themeNames --- .../presentation/templates/themeselector.tpl.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/templates/presentation/templates/themeselector.tpl.html b/tools/templates/presentation/templates/themeselector.tpl.html index e7ac8ae08..72c0de1bc 100644 --- a/tools/templates/presentation/templates/themeselector.tpl.html +++ b/tools/templates/presentation/templates/themeselector.tpl.html @@ -3,11 +3,11 @@
From 386d99388bd6fe9f62e42c01a090c4251729690d Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 007/152] feat(theme): remove file extension for style and squelette option display --- tools/templates/libs/templates.functions.php | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tools/templates/libs/templates.functions.php b/tools/templates/libs/templates.functions.php index faa0cc48f..61e0d75b3 100755 --- a/tools/templates/libs/templates.functions.php +++ b/tools/templates/libs/templates.functions.php @@ -47,8 +47,8 @@ function search_template_files($directory) if (is_dir($directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'styles')) { $dir2 = opendir($directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'styles'); while (false !== ($file2 = readdir($dir2))) { - if (substr($file2, -4, 4)=='.css' || substr($file2, -5, 5)=='.less') { - $tab_themes[$file]["style"][$file2] = $file2; + if (substr($file2, -4, 4) == '.css') { + $tab_themes[$file]["style"][$file2] = remove_extension($file2); } } closedir($dir2); @@ -56,7 +56,7 @@ function search_template_files($directory) $dir3 = opendir($directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'squelettes'); while (false !== ($file3 = readdir($dir3))) { if (substr($file3, -9, 9)=='.tpl.html') { - $tab_themes[$file]["squelette"][$file3]=$file3; + $tab_themes[$file]["squelette"][$file3] = remove_extension($file3); } } closedir($dir3); @@ -87,6 +87,11 @@ function search_template_files($directory) return $tab_themes; } +function remove_extension($filename) +{ + return preg_replace("/\..*/i", '', $filename); +} + /** @@ -351,12 +356,6 @@ function show_form_theme_selector($mode = 'selector', $formclass = '') $bgselector = ''; } - //sort array - $templates = $themeManager->getTemplates(); - ksort($templates[$themeManager->getFavoriteTheme()]['squelette']); - ksort($templates[$themeManager->getFavoriteTheme()]['style']); - $themeManager->setTemplates($templates); - // page list $tablistWikinames = $wiki->LoadAll( 'SELECT DISTINCT tag FROM '.$wiki->GetConfigValue('table_prefix').'pages WHERE latest="Y"' @@ -374,8 +373,7 @@ function show_form_theme_selector($mode = 'selector', $formclass = '') 'id' => $id, 'class' => $formclass, 'bgselector' => $bgselector, - 'themeNames' => array_keys($templates), - 'themes' => $templates, + 'themes' => $themeManager->getTemplates(), 'listWikinames' => $listWikinames, 'favoriteTheme' => $themeManager->getFavoriteTheme(), 'favoriteSquelette' => $themeManager->getFavoriteSquelette(), From 45a03dcac40bee42b47a723fd885776550e07783 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 008/152] Fix loading custom styles even if no squelettes dir --- tools/templates/libs/templates.functions.php | 44 ++++++++++---------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/tools/templates/libs/templates.functions.php b/tools/templates/libs/templates.functions.php index 61e0d75b3..31b025b3a 100755 --- a/tools/templates/libs/templates.functions.php +++ b/tools/templates/libs/templates.functions.php @@ -43,37 +43,39 @@ function search_template_files($directory) $dir = opendir($directory); while ($dir && ($file = readdir($dir)) !== false) { if ($file!='.' && $file!='..' && $file!='CVS' && is_dir($directory.DIRECTORY_SEPARATOR.$file)) { - if (is_dir($directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'squelettes')) { - if (is_dir($directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'styles')) { - $dir2 = opendir($directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'styles'); - while (false !== ($file2 = readdir($dir2))) { - if (substr($file2, -4, 4) == '.css') { - $tab_themes[$file]["style"][$file2] = remove_extension($file2); - } + $pathToStyles = $directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'styles'; + if (is_dir($pathToStyles) && $dir2 = opendir($pathToStyles)) { + while (false !== ($file2 = readdir($dir2))) { + if (substr($file2, -4, 4) == '.css') { + $tab_themes[$file]["style"][$file2] = remove_extension($file2); } - closedir($dir2); } - $dir3 = opendir($directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'squelettes'); + closedir($dir2); + } + + $pathToSquelettes = $directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'squelettes'; + if (is_dir($pathToSquelettes) && $dir3 = opendir($pathToSquelettes)) { while (false !== ($file3 = readdir($dir3))) { if (substr($file3, -9, 9)=='.tpl.html') { $tab_themes[$file]["squelette"][$file3] = remove_extension($file3); } } closedir($dir3); - $pathToPresets = $directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'presets'; - if (is_dir($pathToPresets) && $dir4 = opendir($pathToPresets)) { - while (false !== ($file4 = readdir($dir4))) { - if (substr($file4, -4, 4)=='.css' && file_exists($pathToPresets.'/'.$file4)) { - $css = file_get_contents($pathToPresets.'/'.$file4); - if (!empty($css)) { - $tab_themes[$file]["presets"][$file4] = $css; - } + } + + $pathToPresets = $directory.DIRECTORY_SEPARATOR.$file.DIRECTORY_SEPARATOR.'presets'; + if (is_dir($pathToPresets) && $dir4 = opendir($pathToPresets)) { + while (false !== ($file4 = readdir($dir4))) { + if (substr($file4, -4, 4)=='.css' && file_exists($pathToPresets.'/'.$file4)) { + $css = file_get_contents($pathToPresets.'/'.$file4); + if (!empty($css)) { + $tab_themes[$file]["presets"][$file4] = $css; } } - closedir($dir4); - if (isset($tab_themes[$file]["presets"]) && is_array($tab_themes[$file]["presets"])) { - ksort($tab_themes[$file]["presets"]); - } + } + closedir($dir4); + if (isset($tab_themes[$file]["presets"]) && is_array($tab_themes[$file]["presets"])) { + ksort($tab_themes[$file]["presets"]); } } } From 399e3b565ec0f18ad2befce07f256d1a1b0f356b Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 009/152] feat(bazar-form): title required --- tools/bazar/controllers/FormController.php | 36 +++++++++++++++++----- tools/bazar/lang/bazar_en.inc.php | 1 + tools/bazar/lang/bazar_fr.inc.php | 1 + tools/bazar/services/FormManager.php | 11 +++++-- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/tools/bazar/controllers/FormController.php b/tools/bazar/controllers/FormController.php index 8c742ab6b..ba0392a15 100644 --- a/tools/bazar/controllers/FormController.php +++ b/tools/bazar/controllers/FormController.php @@ -9,6 +9,7 @@ use YesWiki\Core\Controller\CsrfTokenController; use YesWiki\Core\YesWikiController; use YesWiki\Security\Controller\SecurityController; +use \Tamtamchik\SimpleFlash\Flash; class FormController extends YesWikiController { @@ -72,13 +73,18 @@ public function displayAll($message) public function create() { if ($this->wiki->UserIsAdmin()) { - if (isset($_POST['valider'])) { - $this->formManager->create($_POST); + $form = null; - return $this->wiki->redirect($this->wiki->href('', '', ['vue' => 'formulaire', 'msg' => 'BAZ_NOUVEAU_FORMULAIRE_ENREGISTRE'], false)); + if (isset($_POST['valider'])) { + $form = $this->formManager->getFromRawData($_POST); + if ($this->formIsValid($form)) { + $this->formManager->create($_POST); + return $this->wiki->redirect($this->wiki->href('', '', ['vue' => 'formulaire', 'msg' => 'BAZ_NOUVEAU_FORMULAIRE_ENREGISTRE'], false)); + } } return $this->render("@bazar/forms/forms_form.twig", [ + 'form' => $form, 'formAndListIds' => baz_forms_and_lists_ids(), 'groupsList' => $this->getGroupsListIfEnabled(), 'onlyOneEntryOptionAvailable' => $this->formManager->isAvailableOnlyOneEntryOption() @@ -91,14 +97,18 @@ public function create() public function update($id) { if ($this->getService(Guard::class)->isAllowed('saisie_formulaire')) { - if (isset($_POST['valider'])) { - $this->formManager->update($_POST); + $form = $this->formManager->getOne($id); - return $this->wiki->redirect($this->wiki->href('', '', ['vue' => 'formulaire', 'msg' => 'BAZ_FORMULAIRE_MODIFIE'], false)); + if (isset($_POST['valider'])) { + $form = $this->formManager->getFromRawData($_POST); + if ($this->formIsValid($form)) { + $this->formManager->update($_POST); + return $this->wiki->redirect($this->wiki->href('', '', ['vue' => 'formulaire', 'msg' => 'BAZ_FORMULAIRE_MODIFIE'], false)); + } } return $this->render("@bazar/forms/forms_form.twig", [ - 'form' => $this->formManager->getOne($id), + 'form' => $form, 'formAndListIds' => baz_forms_and_lists_ids(), 'groupsList' => $this->getGroupsListIfEnabled(), 'onlyOneEntryOptionAvailable' => $this->formManager->isAvailableOnlyOneEntryOption() && $this->formManager->isAvailableOnlyOneEntryMessage() @@ -108,6 +118,18 @@ public function update($id) } } + private function formIsValid($form) + { + $titleFields = array_filter($form['prepared'], function($field) { + return $field->getPropertyName() == 'bf_titre'; + }); + if (count($titleFields) == 0) { + Flash::error(_t('BAZ_FORM_NEED_TITLE')); + return false; + } + return true; + } + public function delete($id) { if ($this->wiki->UserIsAdmin()) { diff --git a/tools/bazar/lang/bazar_en.inc.php b/tools/bazar/lang/bazar_en.inc.php index a76d3e57c..0d9df0425 100755 --- a/tools/bazar/lang/bazar_en.inc.php +++ b/tools/bazar/lang/bazar_en.inc.php @@ -75,6 +75,7 @@ 'BAZ_NOUVEAU_FORMULAIRE_ENREGISTRE' => 'The new form was successfully created.', 'BAZ_NOUVELLE_LISTE_ENREGISTREE' => 'The new list was successfully created.', 'BAZ_FORMULAIRE_MODIFIE' => 'The form was successfully modified.', + 'BAZ_FORM_NEED_TITLE' => 'You need to add a title to the form, etiher by adding a field with "bf_titre" identifier, or by adding an automatic title field', 'BAZ_LISTE_MODIFIEE' => 'The list was successfully modified.', 'BAZ_CONFIRM_SUPPRIMER_FICHE' => 'Are you sure you want to delete this entry ?', 'BAZ_FICHE_SUPPRIMEE' => 'The entry was successfully deleted.', diff --git a/tools/bazar/lang/bazar_fr.inc.php b/tools/bazar/lang/bazar_fr.inc.php index cc80c68a8..3eab07135 100755 --- a/tools/bazar/lang/bazar_fr.inc.php +++ b/tools/bazar/lang/bazar_fr.inc.php @@ -75,6 +75,7 @@ 'BAZ_NOUVEAU_FORMULAIRE_ENREGISTRE' => 'Le nouveau formulaire a bien été enregistré.', 'BAZ_NOUVELLE_LISTE_ENREGISTREE' => 'La nouvelle liste a bien été enregistrée.', 'BAZ_FORMULAIRE_MODIFIE' => 'Le formulaire a bien été modifié.', + 'BAZ_FORM_NEED_TITLE' => 'Votre formulaire doit contenir un titre. Vous pouvez soit ajouter un champ texte avec comme identifiant "bf_titre", soit un champ "titre automatique".', 'BAZ_LISTE_MODIFIEE' => 'La liste a bien été modifiée.', 'BAZ_CONFIRM_SUPPRIMER_FICHE' => 'Etes vous sûr de vouloir supprimer la fiche ?', 'BAZ_FICHE_SUPPRIMEE' => 'La fiche a bien été supprimée.', diff --git a/tools/bazar/services/FormManager.php b/tools/bazar/services/FormManager.php index 2ef86b906..65759ef40 100644 --- a/tools/bazar/services/FormManager.php +++ b/tools/bazar/services/FormManager.php @@ -54,6 +54,15 @@ public function getOne($formId): ?array return null; } + $form = $this->getFromRawData($form); + + $this->cachedForms[$formId] = $form; + + return $form; + } + + public function getFromRawData($form) + { foreach ($form as $key => $value) { $form[$key] = _convert($value, 'ISO-8859-15'); } @@ -61,8 +70,6 @@ public function getOne($formId): ?array $form['template'] = $this->parseTemplate($form['bn_template']); $form['prepared'] = $this->prepareData($form); - $this->cachedForms[$formId] = $form; - return $form; } From 885cf891d71105298bea32ad48c7e3be3066357b Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 010/152] feat(bazar-form): disable changing bf_titre identifier --- tools/bazar/presentation/javascripts/form-edit-template.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template.js index a72e26b6c..b339fbba0 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template.js @@ -1353,6 +1353,13 @@ function initializeFormbuilder(formAndListIds) { } }) + // disable bf_titre identifier + $('.fld-name').each(function() { + if ($(this).val() === 'bf_titre') { + $(this).attr('disabled', true) + } + }) + // Each 300ms update the text field converting form bulder content into wiki syntax let formBuilderInitialized = false let existingFieldsNames = []; let From 7151526f137e4eed53af72c88c2ebbceeca5ff44 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 011/152] feat(list): fix label too long --- tools/bazar/lang/bazar_en.inc.php | 1 + tools/bazar/lang/bazar_fr.inc.php | 3 +- tools/bazar/presentation/styles/bazar.css | 5 +- tools/bazar/templates/lists/list_form.twig | 74 ++++++++++++---------- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/tools/bazar/lang/bazar_en.inc.php b/tools/bazar/lang/bazar_en.inc.php index 0d9df0425..41c02366a 100755 --- a/tools/bazar/lang/bazar_en.inc.php +++ b/tools/bazar/lang/bazar_en.inc.php @@ -6,6 +6,7 @@ 'BAZ_LISTES' => 'Lists', 'BAZ_NOM_LISTE' => 'Name of the list', 'BAZ_VALEURS_LISTE' => 'Values of the list', + 'BAZ_VALEURS_LISTE_HINT' => 'Key should be unique, without spaces nor any special characters', 'BAZ_AJOUTER_LABEL_LISTE' => 'Add a new value to the list', 'BAZ_AJOUTER_CHAMPS_FORMULAIRE' => 'Add a new field to the form', 'BAZ_MODIFIER_FORMULAIRES' => 'Forms administration', diff --git a/tools/bazar/lang/bazar_fr.inc.php b/tools/bazar/lang/bazar_fr.inc.php index 3eab07135..31f65baec 100755 --- a/tools/bazar/lang/bazar_fr.inc.php +++ b/tools/bazar/lang/bazar_fr.inc.php @@ -5,7 +5,8 @@ 'BAZ_FORMULAIRE' => 'Formulaires', 'BAZ_LISTES' => 'Listes', 'BAZ_NOM_LISTE' => 'Nom de la liste', - 'BAZ_VALEURS_LISTE' => 'Valeurs de la liste (pour la clé pas d\'accent, de caractères spéciaux, d\'espace. Chaque clé sera unique. Ex : aut pour autre, pub pour public', + 'BAZ_VALEURS_LISTE' => 'Valeurs de la liste', + 'BAZ_VALEURS_LISTE_HINT' => 'Pour la clé pas d\'accent, de caractères spéciaux, d\'espace. Chaque clé sera unique.', 'BAZ_AJOUTER_LABEL_LISTE' => 'Ajouter une nouvelle valeur à la liste', 'BAZ_AJOUTER_CHAMPS_FORMULAIRE' => 'Ajouter un nouveau champs au formulaire', 'BAZ_MODIFIER_FORMULAIRES' => 'Gestion des formulaires', diff --git a/tools/bazar/presentation/styles/bazar.css b/tools/bazar/presentation/styles/bazar.css index d6e65437d..8d9b3f55a 100644 --- a/tools/bazar/presentation/styles/bazar.css +++ b/tools/bazar/presentation/styles/bazar.css @@ -165,7 +165,10 @@ input.form-control.error { .BAZ_label {font-weight: bold;display: block;} .BAZ_texte {display:block;} .titre_lien {display:block;font-size:1.4em;} -.list-sortables {list-style: none;} +.list-sortables { + list-style: none; + padding: 0; +} .list-sortables .liste_ligne { display: table; margin-bottom: 3px; diff --git a/tools/bazar/templates/lists/list_form.twig b/tools/bazar/templates/lists/list_form.twig index 25ea6e2ee..79190476f 100755 --- a/tools/bazar/templates/lists/list_form.twig +++ b/tools/bazar/templates/lists/list_form.twig @@ -27,10 +27,37 @@ -
    - {% if labels %} - {% for key, label in labels %} -
  • +
    +
      + {% if labels %} + {% for key, label in labels %} +
    • + + + + + + + + +
    • + {% endfor %} + {% else %} +
    • @@ -38,48 +65,25 @@ required type="text" placeholder="{{ _t('BAZ_KEY') }}" - name="id[{{ loop.index }}]" - value="{{ key|e }}" + name="id[1]" class="input-key form-control" /> -
    • - {% endfor %} - {% else %} -
    • - - - - - - - - -
    • - {% endif %} -
    +
  • + {% endif %} +
+
{{ _t('BAZ_VALEURS_LISTE_HINT') }}
+
+
From 0e834c8771df7e3db5b966fe26ab2777c5846782 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 012/152] feat(form): linting --- .../javascripts/form-edit-template.js | 580 +++++++++--------- 1 file changed, 279 insertions(+), 301 deletions(-) diff --git a/tools/bazar/presentation/javascripts/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template.js index b339fbba0..25c63d5e2 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template.js @@ -1,13 +1,13 @@ -var $formBuilderTextInput = $('#form-builder-text') -var $formBuilderContainer = $('#form-builder-container') +const $formBuilderTextInput = $('#form-builder-text') +const $formBuilderContainer = $('#form-builder-container') let formBuilder // When user add manuall via wikiCode a list or a formId that does not exist, keep the value // so it can be added the select option list -var listAndFormUserValues = {} +const listAndFormUserValues = {} // Fill the listAndFormUserValues -var text = $formBuilderTextInput.val().trim() -var textFields = text.split('\n') +const text = $formBuilderTextInput.val().trim() +const textFields = text.split('\n') for (var i = 0; i < textFields.length; i++) { const textField = textFields[i] const fieldValues = textField.split('***') @@ -30,35 +30,13 @@ for (var i = 0; i < textFields.length; i++) { } } // Custom fields to add to form builder -var fields = [ - { - label: _t('BAZ_FORM_EDIT_TEXTAREA_LABEL'), - name: 'textarea', - attrs: { type: 'textarea' }, - icon: - '' - }, - { - label: _t('BAZ_FORM_EDIT_SELECT_LABEL'), - name: 'select', - attrs: { type: 'select' }, - icon: - '' - }, - { - label: _t('BAZ_FORM_EDIT_RADIO_LABEL'), - name: 'radio-group', - attrs: { type: 'radio-group' }, - icon: - '' - }, - { - label: _t('BAZ_FORM_EDIT_CHECKBOX_LABEL'), - name: 'checkbox-group', - attrs: { type: 'checkbox-group' }, - icon: - '' - }, +const fields = [ + // { + // label: "Sélecteur de date", + // name: "jour", + // attrs: { type: "date" }, + // icon: '', + // }, { label: _t('BAZ_FORM_EDIT_TEXT_LABEL'), name: 'text', @@ -168,21 +146,21 @@ var fields = [ }, { label: _t('BAZ_REACTIONS_FIELD'), - name: "reactions", - attrs: { type: "reactions" }, - icon: '', + name: 'reactions', + attrs: { type: 'reactions' }, + icon: '' } ] // Some attributes configuration used in multiple fields -var visibilityOptions = { +const visibilityOptions = { ' * ': _t('EVERYONE'), ' + ': _t('IDENTIFIED_USERS'), ' % ': _t('BAZ_FORM_EDIT_OWNER_AND_ADMINS'), '@admins': _t('MEMBER_OF_GROUP', { groupName: 'admin' }) } // create list of groups -var formattedGroupList = [] +const formattedGroupList = [] if (groupsList && groupsList.length > 0) { const groupsListLen = groupsList.length for (i = 0; i < groupsListLen; ++i) { @@ -192,7 +170,7 @@ if (groupsList && groupsList.length > 0) { } } -var aclsOptions = { +const aclsOptions = { ...visibilityOptions, ...{ user: @@ -200,24 +178,24 @@ var aclsOptions = { }, ...formattedGroupList } -var aclsCommentOptions = { +const aclsCommentOptions = { ...{ 'comments-closed': _t('BAZ_FORM_EDIT_COMMENTS_CLOSED') }, ...visibilityOptions, ...{ user: _t('BAZ_FORM_EDIT_USER') }, ...formattedGroupList } -var readConf = { label: _t('BAZ_FORM_EDIT_CAN_BE_READ_BY'), options: { ...visibilityOptions, ...formattedGroupList }, multiple: true } -var writeconf = { label: _t('BAZ_FORM_EDIT_CAN_BE_WRITTEN_BY'), options: { ...visibilityOptions, ...formattedGroupList }, multiple: true } -var searchableConf = { +const readConf = { label: _t('BAZ_FORM_EDIT_CAN_BE_READ_BY'), options: { ...visibilityOptions, ...formattedGroupList }, multiple: true } +const writeconf = { label: _t('BAZ_FORM_EDIT_CAN_BE_WRITTEN_BY'), options: { ...visibilityOptions, ...formattedGroupList }, multiple: true } +const searchableConf = { label: _t('BAZ_FORM_EDIT_SEARCH_LABEL'), options: { '': _t('NO'), 1: _t('YES') } } -var semanticConf = { +const semanticConf = { label: _t('BAZ_FORM_EDIT_SEMANTIC_LABEL'), value: '', placeholder: 'Ex: https://schema.org/name' } -var selectConf = { +const selectConf = { subtype2: { label: _t('BAZ_FORM_EDIT_SELECT_SUBTYPE2_LABEL'), options: { @@ -252,7 +230,7 @@ var selectConf = { semantic: semanticConf // searchable: searchableConf -> 10/19 Florian say that this conf is not working for now } -var TabsConf = { +const TabsConf = { formTitles: { label: _t('BAZ_FORM_EDIT_TABS_FOR_FORM'), value: _t('BAZ_FORM_EDIT_TABS_FORMTITLES_VALUE'), @@ -279,7 +257,7 @@ var TabsConf = { options: { '': _t('NORMAL_F'), 'btn-xs': _t('SMALL_F') } } } -var TabChangeConf = { +const TabChangeConf = { formChange: { label: _t('BAZ_FORM_EDIT_TABS_FOR_FORM'), options: { formChange: _t('YES'), noformchange: _t('NO') }, @@ -293,7 +271,7 @@ var TabChangeConf = { } // Attributes to be configured for each field -var typeUserAttrs = { +let typeUserAttrs = { text: { size: { label: _t('BAZ_FORM_EDIT_TEXT_SIZE'), value: '' }, maxlength: { label: _t('BAZ_FORM_EDIT_TEXT_MAX_LENGTH'), value: '' }, @@ -333,11 +311,11 @@ var typeUserAttrs = { }, replace_email_by_button: { label: _t('BAZ_FORM_EDIT_EMAIL_REPLACE_BY_BUTTON_LABEL'), - options: { '': _t('NO'), 'form': _t('YES') }, + options: { '': _t('NO'), form: _t('YES') }, value: 'form' }, - seeEmailAcls: {...readConf,...{label:_t('BAZ_FORM_EDIT_EMAIL_SEE_MAIL_ACLS')}}, - readWhenForm: {...readConf,...{label:_t('BAZ_FORM_EDIT_EMAIL_SEND_ACLS')}}, + seeEmailAcls: { ...readConf, ...{ label: _t('BAZ_FORM_EDIT_EMAIL_SEE_MAIL_ACLS') } }, + readWhenForm: { ...readConf, ...{ label: _t('BAZ_FORM_EDIT_EMAIL_SEND_ACLS') } }, // searchable: searchableConf, -> 10/19 Florian say that this conf is not working for now read: readConf, write: writeconf, @@ -351,7 +329,7 @@ var typeUserAttrs = { autocomplete_town: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_TOWN'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_TOWN_PLACEHOLDER') }, autocomplete_county: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_COUNTY'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_COUNTY_PLACEHOLDER') }, autocomplete_state: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STATE'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STATE_PLACEHOLDER') }, - autocomplete_other: { label: '', value: ''}, + autocomplete_other: { label: '', value: '' }, autocomplete_street1: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET1'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET1_PLACEHOLDER') }, autocomplete_street2: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET2'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET2_PLACEHOLDER') }, geolocate: { @@ -507,24 +485,24 @@ var typeUserAttrs = { read: { label: _t('BAZ_FORM_EDIT_ACL_READ_LABEL'), options: aclsOptions, multiple: true }, write: { label: _t('BAZ_FORM_EDIT_ACL_WRITE_LABEL'), options: aclsOptions, multiple: true }, comment: { label: _t('BAZ_FORM_EDIT_ACL_COMMENT_LABEL'), options: aclsCommentOptions, multiple: true }, - askIfActivateComments: { - label: _t('BAZ_FORM_EDIT_ACL_ASK_IF_ACTIVATE_COMMENT_LABEL'), - options: { 0: _t('NO'), 1: _t('YES') }, + askIfActivateComments: { + label: _t('BAZ_FORM_EDIT_ACL_ASK_IF_ACTIVATE_COMMENT_LABEL'), + options: { 0: _t('NO'), 1: _t('YES') } }, fieldLabel: { - label: _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_LABEL'), - value: "", + label: _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_LABEL'), + value: '', placeholder: _t('BAZ_ACTIVATE_COMMENTS') }, hint: { - label: _t('BAZ_FORM_EDIT_HELP'), + label: _t('BAZ_FORM_EDIT_HELP'), value: '', placeholder: _t('BAZ_ACTIVATE_COMMENTS_HINT') }, value: { - label: _t('BAZ_FORM_EDIT_COMMENTS_FIELD_DEFAULT_ACTIVATION_LABEL'), - options: {non: _t('NO'), oui: _t('YES'), ' ': ''} - }, + label: _t('BAZ_FORM_EDIT_COMMENTS_FIELD_DEFAULT_ACTIVATION_LABEL'), + options: { non: _t('NO'), oui: _t('YES'), ' ': '' } + } }, metadatas: { theme: { @@ -576,26 +554,26 @@ var typeUserAttrs = { }, reactions: { fieldlabel: { - label: _t('BAZ_REACTIONS_FIELD_ACTIVATE_LABEL'), - value: "", + label: _t('BAZ_REACTIONS_FIELD_ACTIVATE_LABEL'), + value: '', placeholder: _t('BAZ_ACTIVATE_REACTIONS') }, value: { - label: _t('BAZ_REACTIONS_FIELD_DEFAULT_ACTIVATION_LABEL'), - options: { oui: _t('YES'),non: _t('NO') } + label: _t('BAZ_REACTIONS_FIELD_DEFAULT_ACTIVATION_LABEL'), + options: { oui: _t('YES'), non: _t('NO') } }, labels: { - label: _t('BAZ_REACTIONS_FIELD_LABELS_LABEL'), - value: "" + label: _t('BAZ_REACTIONS_FIELD_LABELS_LABEL'), + value: '' }, images: { - label: _t('BAZ_REACTIONS_FIELD_IMAGES_LABEL'), - value: "", + label: _t('BAZ_REACTIONS_FIELD_IMAGES_LABEL'), + value: '', placeholder: _t('BAZ_REACTIONS_FIELD_IMAGES_PLACEHOLDER') }, ids: { - label: _t('BAZ_REACTIONS_FIELD_IDS_LABEL'), - value: "" + label: _t('BAZ_REACTIONS_FIELD_IDS_LABEL'), + value: '' }, read: readConf, write: writeconf, @@ -624,84 +602,84 @@ var typeUserAttrs = { } // How a field is represented in the formBuilder view -var templates = { +let templates = { champs_mail(fieldData) { - return { + return { field: ``, onRender() { - let currentField = templateHelper.getHolder(fieldData).parent() + const currentField = templateHelper.getHolder(fieldData).parent() templateHelper.initializeField(currentField) - const arrayEquals = (a,b)=>{ - if (a.length != b.length){ + const arrayEquals = (a, b) => { + if (a.length != b.length) { return false } - return (a.every((e)=>b.includes(e)) && b.every((e)=>a.includes(e))) + return (a.every((e) => b.includes(e)) && b.every((e) => a.includes(e))) } - currentField.find("select[name=read]:not(.initialized)") - .on('change',(event)=>{ - const element = event.target - const base = $(element).closest(".champs_mail-field.form-field") - $(element).addClass("initialized") + currentField.find('select[name=read]:not(.initialized)') + .on('change', (event) => { + const element = event.target + const base = $(element).closest('.champs_mail-field.form-field') + $(element).addClass('initialized') - const readWhenFormInput = $(base).find("select[name=readWhenForm]") - if (readWhenFormInput && readWhenFormInput.length > 0 && !arrayEquals(readWhenFormInput.val(),$(element).val())){ - readWhenFormInput.val($(element).val()) - } - }).trigger("change") - currentField.find("select[name=readWhenForm]:not(.initialized)") - .on('change',(event)=>{ - const element = event.target - const base = $(element).closest(".champs_mail-field.form-field") - $(element).addClass("initialized") + const readWhenFormInput = $(base).find('select[name=readWhenForm]') + if (readWhenFormInput && readWhenFormInput.length > 0 && !arrayEquals(readWhenFormInput.val(), $(element).val())) { + readWhenFormInput.val($(element).val()) + } + }).trigger('change') + currentField.find('select[name=readWhenForm]:not(.initialized)') + .on('change', (event) => { + const element = event.target + const base = $(element).closest('.champs_mail-field.form-field') + $(element).addClass('initialized') - const readInput = $(base).find("select[name=read]") - if (readInput && readInput.length > 0 && !arrayEquals(readInput.val(),$(element).val())){ - readInput.val($(element).val()) - } - }).trigger("change") + const readInput = $(base).find('select[name=read]') + if (readInput && readInput.length > 0 && !arrayEquals(readInput.val(), $(element).val())) { + readInput.val($(element).val()) + } + }).trigger('change') currentField - .find("select[name=replace_email_by_button]:not(.initialized)") - .on('change',(event)=>{ + .find('select[name=replace_email_by_button]:not(.initialized)') + .on('change', (event) => { const element = event.target - const base = $(element).closest(".champs_mail-field.form-field") - $(element).addClass("initialized") + const base = $(element).closest('.champs_mail-field.form-field') + $(element).addClass('initialized') - const setDisplay = (base,name,newValue)=>{ - let wrapper = $(base).find(`div.form-group.${name}-wrap`) - if (wrapper && wrapper.length > 0){ - if(newValue){ + const setDisplay = (base, name, newValue) => { + const wrapper = $(base).find(`div.form-group.${name}-wrap`) + if (wrapper && wrapper.length > 0) { + if (newValue) { wrapper.show() } else { wrapper.hide() } } } - if ($(element).val() == 'form'){ + if ($(element).val() == 'form') { // when chosing 'form' (or at init), if readAcl is ' % ', prefer ' * ' // to show button to everyone - let field = currentField.find("select[name=read]") - if (arrayEquals(field.val(),[' % '])){ + const field = currentField.find('select[name=read]') + if (arrayEquals(field.val(), [' % '])) { field.val([' * ']) field.trigger('change') } - setDisplay(base,'readWhenForm',1) - setDisplay(base,'seeEmailAcls',1) - setDisplay(base,'read',0) + setDisplay(base, 'readWhenForm', 1) + setDisplay(base, 'seeEmailAcls', 1) + setDisplay(base, 'read', 0) } else { // when chosing 'text' (or at init), if readAcl is ' * ', prefer ' % ' // to force email not to be shown - let field = currentField.find("select[name=read]") - if (arrayEquals(field.val(),[' * ']) && !currentField.find("select[name=write]").val().includes(' * ')){ + const field = currentField.find('select[name=read]') + if (arrayEquals(field.val(), [' * ']) && !currentField.find('select[name=write]').val().includes(' * ')) { field.val([' % ']) field.trigger('change') } - setDisplay(base,'readWhenForm',0) - setDisplay(base,'seeEmailAcls',0) - setDisplay(base,'read',1) + setDisplay(base, 'readWhenForm', 0) + setDisplay(base, 'seeEmailAcls', 0) + setDisplay(base, 'read', 1) } }) - .trigger("change") + .trigger('change') } } }, @@ -709,134 +687,134 @@ var templates = { return { field: _t('BAZ_FORM_EDIT_MAP_FIELD'), onRender() { - const toggleState = function (name,state){ - const formGroup = templateHelper.getFormGroup(fieldData, name) - if (formGroup !== null){ - if (state === 'show'){ - formGroup.show() - } else { - formGroup.hide() - } + const toggleState = function(name, state) { + const formGroup = templateHelper.getFormGroup(fieldData, name) + if (formGroup !== null) { + if (state === 'show') { + formGroup.show() + } else { + formGroup.hide() } } - const toggleStates = function (state){ - ['autocomplete_street1','autocomplete_street2'].forEach((name)=>toggleState(name,state)) - } - // initMapAutocompleteUpdate() - $(".map-field.form-field") - .find("input[type=text][name=autocomplete_street]:not(.initialized)" - +",input[type=text][name=autocomplete_street1]:not(.initialized)" - +",input[type=text][name=autocomplete_street2]:not(.initialized)" - +",input[type=text][name=autocomplete_county]:not(.initialized)" - +",input[type=text][name=autocomplete_state]:not(.initialized)" - +",select[name=geolocate]:not(.initialized)") - .on('change',function(event){ + } + const toggleStates = function(state) { + ['autocomplete_street1', 'autocomplete_street2'].forEach((name) => toggleState(name, state)) + } + // initMapAutocompleteUpdate() + $('.map-field.form-field') + .find('input[type=text][name=autocomplete_street]:not(.initialized)' + + ',input[type=text][name=autocomplete_street1]:not(.initialized)' + + ',input[type=text][name=autocomplete_street2]:not(.initialized)' + + ',input[type=text][name=autocomplete_county]:not(.initialized)' + + ',input[type=text][name=autocomplete_state]:not(.initialized)' + + ',select[name=geolocate]:not(.initialized)') + .on('change', (event) => { // mapAutocompleteUpdate(event.target) const element = event.target - const base = $(element).closest(".map-field.form-field") - if (!$(element).hasClass("initialized")){ - $(element).addClass("initialized"); - if ($(element).val().length == 0 || $(element).prop('tagName') === 'SELECT'){ - // mapAutocompleteUpdateExtractFromOther(base) - var other = { - geolocate: '', - street: '', - street1: '', - street2: '', - county: '', - state: '' - } - const autoCompleteOther = $(base) - .find("input[type=text][name=autocomplete_other]") - .first() - if (autoCompleteOther && autoCompleteOther.length > 0){ - const value = autoCompleteOther.val().split('|') - other.geolocate = ['1',1,true].includes(value[0]) ? '1' : '0' - other.street = value[1] || '' - other.street1 = value[2] || '' - other.street2 = value[3] || '' - other.county = value[4] || '' - other.state = value[5] || '' - } - switch (element.getAttribute('name')) { - case 'autocomplete_street': - $(element).val(other.street) - break; - case 'autocomplete_street1': - $(element).val(other.street1) - break; - case 'autocomplete_street2': - $(element).val(other.street2) - break; - case 'autocomplete_county': - $(element).val(other.county) - break; - case 'autocomplete_state': - $(element).val(other.state) - break; - case 'geolocate': - $(element).val(other.geolocate === '1' ? '1' : '0') - break; - default: - break; - } + const base = $(element).closest('.map-field.form-field') + if (!$(element).hasClass('initialized')) { + $(element).addClass('initialized') + if ($(element).val().length == 0 || $(element).prop('tagName') === 'SELECT') { + // mapAutocompleteUpdateExtractFromOther(base) + const other = { + geolocate: '', + street: '', + street1: '', + street2: '', + county: '', + state: '' } - } else { - // autocompleteUpdateSaveToOther(base) const autoCompleteOther = $(base) - .find("input[type=text][name=autocomplete_other]") + .find('input[type=text][name=autocomplete_other]') + .first() + if (autoCompleteOther && autoCompleteOther.length > 0) { + const value = autoCompleteOther.val().split('|') + other.geolocate = ['1', 1, true].includes(value[0]) ? '1' : '0' + other.street = value[1] || '' + other.street1 = value[2] || '' + other.street2 = value[3] || '' + other.county = value[4] || '' + other.state = value[5] || '' + } + switch (element.getAttribute('name')) { + case 'autocomplete_street': + $(element).val(other.street) + break + case 'autocomplete_street1': + $(element).val(other.street1) + break + case 'autocomplete_street2': + $(element).val(other.street2) + break + case 'autocomplete_county': + $(element).val(other.county) + break + case 'autocomplete_state': + $(element).val(other.state) + break + case 'geolocate': + $(element).val(other.geolocate === '1' ? '1' : '0') + break + default: + break + } + } + } else { + // autocompleteUpdateSaveToOther(base) + const autoCompleteOther = $(base) + .find('input[type=text][name=autocomplete_other]') + .first() + if (autoCompleteOther && autoCompleteOther.length > 0) { + const results = { + geolocate: '', + street: '', + street1: '', + street2: '', + county: '', + state: '' + } + const associations = { + street: 'autocomplete_street', + street1: 'autocomplete_street1', + street2: 'autocomplete_street2', + county: 'autocomplete_county', + state: 'autocomplete_state' + } + for (const key in associations) { + const autoCompleteField = $(base) + .find(`input[type=text][name=${associations[key]}]`) .first() - if (autoCompleteOther && autoCompleteOther.length > 0){ - var results = { - geolocate: '', - street: '', - street1: '', - street2: '', - county: '', - state: '' - } - const associations = { - street: 'autocomplete_street', - street1: 'autocomplete_street1', - street2: 'autocomplete_street2', - county: 'autocomplete_county', - state: 'autocomplete_state' - } - for (const key in associations) { - const autoCompleteField = $(base) - .find(`input[type=text][name=${associations[key]}]`) - .first() - if (autoCompleteField && autoCompleteField.length > 0){ - results[key] = autoCompleteField.val() || '' - } - } - // geolocate - const geolocateField = $(base) - .find('select[name=geolocate]') - .first() - if (geolocateField && geolocateField.length > 0){ - results.geolocate = geolocateField.val() || '' - } - autoCompleteOther.val( - results.geolocate - + `|${results.street}` + if (autoCompleteField && autoCompleteField.length > 0) { + results[key] = autoCompleteField.val() || '' + } + } + // geolocate + const geolocateField = $(base) + .find('select[name=geolocate]') + .first() + if (geolocateField && geolocateField.length > 0) { + results.geolocate = geolocateField.val() || '' + } + autoCompleteOther.val( + `${results.geolocate + }|${results.street}` + `|${results.street1}` + `|${results.street2}` + `|${results.county}` + `|${results.state}` - ) - } + ) + } } }) .trigger('change') - templateHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street', ` + templateHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street', `
${_t('GEOLOCATER_GROUP_GEOLOCATIZATION')}
${_t('GEOLOCATER_GROUP_GEOLOCATIZATION_HINT')}
`) - $advancedParams = $(` + $advancedParams = $(`
@@ -844,22 +822,22 @@ var templates = {
`) - templateHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street1', $advancedParams) - $advancedParams.find('button').on('click', function (event) { - if ($(this).hasClass('opened')){ - $(this).removeClass('opened') - $(this).html(_t('GEOLOCATER_SEE_ADVANCED_PARAMS')); - toggleStates('hide') - } else { - $(this).addClass('opened') - $(this).html(_t('GEOLOCATER_HIDE_ADVANCED_PARAMS')); - toggleStates('show') - } - event.preventDefault() - event.stopPropagation() - }) - toggleStates('hide') - templateHelper.prependHTMLBeforeGroup(fieldData, 'geolocate', '

') + templateHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street1', $advancedParams) + $advancedParams.find('button').on('click', function(event) { + if ($(this).hasClass('opened')) { + $(this).removeClass('opened') + $(this).html(_t('GEOLOCATER_SEE_ADVANCED_PARAMS')) + toggleStates('hide') + } else { + $(this).addClass('opened') + $(this).html(_t('GEOLOCATER_HIDE_ADVANCED_PARAMS')) + toggleStates('show') + } + event.preventDefault() + event.stopPropagation() + }) + toggleStates('hide') + templateHelper.prependHTMLBeforeGroup(fieldData, 'geolocate', '

') } } }, @@ -898,46 +876,46 @@ var templates = { } }, acls(field) { - return { - field: field.askIfActivateComments == 1 ? ` ${field.fieldlabel || _t('BAZ_ACTIVATE_COMMENTS')}` : '' , + return { + field: field.askIfActivateComments == 1 ? ` ${field.fieldlabel || _t('BAZ_ACTIVATE_COMMENTS')}` : '', onRender() { - let currentField = templateHelper.getHolder(field).parent() + const currentField = templateHelper.getHolder(field).parent() templateHelper.initializeField(currentField) $(currentField) - .find("select[name=askIfActivateComments]:not(.initialized)") - .change(function(event){ + .find('select[name=askIfActivateComments]:not(.initialized)') + .change((event) => { const element = event.target - const base = $(element).closest(".acls-field.form-field") - $(element).addClass("initialized") + const base = $(element).closest('.acls-field.form-field') + $(element).addClass('initialized') - var nameInput = $(base).find("input[type=text][name=name]") - if (nameInput.val().trim().length == 0 || - nameInput.val().trim() == 'bf_acls' ){ + const nameInput = $(base).find('input[type=text][name=name]') + if (nameInput.val().trim().length == 0 + || nameInput.val().trim() == 'bf_acls') { nameInput.val('bf_commentaires') } - - var visibleSelect = $(base).find("select[name=askIfActivateComments]") - var selectedValue = visibleSelect.val() - - var subElements = $(base) - .find(".form-group.fieldLabel-wrap,.form-group.hint-wrap,.form-group.name-wrap,.form-group.value-wrap") - if ([1,'1'].includes(selectedValue)){ + + const visibleSelect = $(base).find('select[name=askIfActivateComments]') + const selectedValue = visibleSelect.val() + + const subElements = $(base) + .find('.form-group.fieldLabel-wrap,.form-group.hint-wrap,.form-group.name-wrap,.form-group.value-wrap') + if ([1, '1'].includes(selectedValue)) { subElements.show() - var commentInput = $(base).find("select[name=comment]") - var currentValue = commentInput.val() - if (Array.isArray(currentValue) && - ( - currentValue.length == 0 || - (currentValue.length == 1 && currentValue.includes('comments-closed')) - )){ + const commentInput = $(base).find('select[name=comment]') + const currentValue = commentInput.val() + if (Array.isArray(currentValue) + && ( + currentValue.length == 0 + || (currentValue.length == 1 && currentValue.includes('comments-closed')) + )) { commentInput.val([' + ']) } } else { subElements.hide() } }) - .trigger("change"); + .trigger('change') templateHelper.defineLabelHintForGroup(field, 'fieldlabel', _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_HINT')) templateHelper.defineLabelHintForGroup(field, 'hint', _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_HINT')) } @@ -1006,28 +984,28 @@ var templates = { } } }, - reactions: function (field) { - return { - field: ` ${field.fieldlabel || _t('BAZ_ACTIVATE_REACTIONS')}` , + reactions(field) { + return { + field: ` ${field.fieldlabel || _t('BAZ_ACTIVATE_REACTIONS')}`, onRender() { - templateHelper.defineLabelHintForGroup(field, 'fieldlabel', _t('BAZ_REACTIONS_FIELD_ACTIVATE_HINT')) - templateHelper.defineLabelHintForGroup(field, 'ids', _t('BAZ_REACTIONS_FIELD_IDS_HINT')) - templateHelper.defineLabelHintForGroup(field, 'images', _t('BAZ_REACTIONS_FIELD_IMAGES_HINT')) - templateHelper.defineLabelHintForGroup(field, 'labels', _t('BAZ_REACTIONS_FIELD_LABELS_HINT')) + templateHelper.defineLabelHintForGroup(field, 'fieldlabel', _t('BAZ_REACTIONS_FIELD_ACTIVATE_HINT')) + templateHelper.defineLabelHintForGroup(field, 'ids', _t('BAZ_REACTIONS_FIELD_IDS_HINT')) + templateHelper.defineLabelHintForGroup(field, 'images', _t('BAZ_REACTIONS_FIELD_IMAGES_HINT')) + templateHelper.defineLabelHintForGroup(field, 'labels', _t('BAZ_REACTIONS_FIELD_LABELS_HINT')) } } } } -var typeUserDisabledAttrs = { +let typeUserDisabledAttrs = { tabs: ['required', 'value', 'name', 'label'], tabchange: ['required', 'value', 'name', 'label'], bookmarklet: ['required', 'value'], - reactions: ['label','required'], - acls: ['label','required'] + reactions: ['label', 'required'], + acls: ['label', 'required'] } -var inputSets = [ +const inputSets = [ { label: _t('BAZ_FORM_EDIT_TABS'), name: 'tabs', @@ -1054,7 +1032,7 @@ var inputSets = [ ] // Mapping betwwen yes wiki syntax and FormBuilder json syntax -var defaultMapping = { +const defaultMapping = { 0: 'type', 1: 'name', 2: 'label', @@ -1071,17 +1049,17 @@ var defaultMapping = { 14: 'semantic', 15: 'queries' } -var lists = { +const lists = { ...defaultMapping, ...{ 1: 'listeOrFormId', 5: 'defaultValue', 6: 'name' } } -var yesWikiMapping = { +let yesWikiMapping = { text: defaultMapping, url: defaultMapping, number: defaultMapping, champs_mail: { ...defaultMapping, - ...{ 4:'seeEmailAcls', 6: 'replace_email_by_button', 9: 'send_form_content_to_this_email' } + ...{ 4: 'seeEmailAcls', 6: 'replace_email_by_button', 9: 'send_form_content_to_this_email' } }, map: { 0: 'type', @@ -1126,13 +1104,13 @@ var yesWikiMapping = { titre: { 0: 'type', 1: 'value', 2: 'label' }, acls: { 0: 'type', - 1: 'read', - 2: 'write', + 1: 'read', + 2: 'write', 3: 'comment', 4: 'fieldLabel', 5: 'value', 6: 'name', - 7: "askIfActivateComments", + 7: 'askIfActivateComments', 8: '', 9: '', 10: 'hint' @@ -1203,15 +1181,15 @@ var yesWikiMapping = { reactions: { ...defaultMapping, ...{ - 2: "ids", - 3: "labels", - 4: "images", - 6: "fieldlabel" + 2: 'ids', + 3: 'labels', + 4: 'images', + 6: 'fieldlabel' } } } // Mapping betwwen yeswiki field type and standard field implemented by form builder -var yesWikiTypes = { +const yesWikiTypes = { lien_internet: { type: 'url' }, lien_internet_bis: { type: 'text', subtype: 'url' }, mot_de_passe: { type: 'text', subtype: 'password' }, @@ -1234,14 +1212,14 @@ var yesWikiTypes = { listefiches: { type: 'listefichesliees' } } -var defaultFieldsName = { +const defaultFieldsName = { textarea: 'bf_description', image: 'bf_image', champs_mail: 'bf_mail', date: 'bf_date_debut_evenement' } -var I18nOption = { +const I18nOption = { ar: 'ar-SA', ca: 'ca-ES', cs: 'cs-CZ', @@ -1291,7 +1269,7 @@ function copyMultipleSelectValues(currentField) { } } -var typeUserEvents = {} +const typeUserEvents = {} for (const key in typeUserAttrs) { typeUserEvents[key] = { onclone: copyMultipleSelectValues } } @@ -1340,7 +1318,7 @@ function initializeFormbuilder(formAndListIds) { field.read = [' * ']// everyone by default } if (!field.hasOwnProperty('write')) { - field.write = (field.type === 'champs_mail') + field.write = (field.type === 'champs_mail') ? [' % '] // owner and @admins by default for e-mail : [' * '] // everyone by default } @@ -1609,17 +1587,17 @@ function parseWikiTextIntoJsonData(text) { if (field == 'required') value = value == '1' if (field) { if (field == 'read' || field == 'write' || field == 'comment') { - fieldObject[field] = (value.trim() === '') + fieldObject[field] = (value.trim() === '') ? ( - field == 'comment' + field == 'comment' ? [' + '] : [' * '] - ) - : value.split(',').map((e)=>(['+','*','%'].includes(e.trim())) ? ` ${e.trim()} ` : e) - } else if (field == 'seeEmailAcls'){ - fieldObject[field] = (value.trim() === '') + ) + : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) + } else if (field == 'seeEmailAcls') { + fieldObject[field] = (value.trim() === '') ? ' % ' // if not define in tempalte, choose owner and admins - : value.split(',').map((e)=>(['+','*','%'].includes(e.trim())) ? ` ${e.trim()} ` : e) + : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) } else { fieldObject[field] = value } From 279e9b8bc18dfa7a819e044ab8947136021400cb Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 013/152] refactor(form-builder): one file per field --- .../conditionschecking.js | 0 .../calcfield/form-edit-template.js | 62 - .../conditionschecking/form-edit-template.js | 80 - .../javascripts/form-edit-template.js | 1624 ----------------- .../form-edit-template/fields/README | 5 + .../form-edit-template/fields/acls.js | 108 ++ .../form-edit-template/fields/bookmarklet.js | 40 + .../form-edit-template/fields/calc.js | 45 + .../form-edit-template/fields/champs_mail.js | 115 ++ .../fields/checkbox-group.js | 31 + .../fields/collaborative_doc.js | 11 + .../fields/commons/attributes.js | 131 ++ .../fields/commons/render-helper.js} | 2 +- .../fields/conditionschecking.js | 46 + .../form-edit-template/fields/custom.js | 48 + .../form-edit-template/fields/date.js | 23 + .../form-edit-template/fields/file.js | 19 + .../form-edit-template/fields/hidden.js | 6 + .../form-edit-template/fields/image.js | 40 + .../fields/inscriptionliste.js | 30 + .../form-edit-template/fields/labelhtml.js | 40 + .../fields/listefichesliees.js | 52 + .../form-edit-template/fields/map.js | 195 ++ .../form-edit-template/fields/metadatas.js | 36 + .../form-edit-template/fields/radio-group.js | 30 + .../form-edit-template/fields/reactions.js | 61 + .../form-edit-template/fields/select.js | 23 + .../form-edit-template/fields/tabchange.js | 47 + .../form-edit-template/fields/tabs.js | 80 + .../form-edit-template/fields/tags.js | 20 + .../form-edit-template/fields/text.js | 48 + .../form-edit-template/fields/textarea.js | 33 + .../form-edit-template/fields/titre.js | 14 + .../form-edit-template/fields/url.js | 19 + .../fields/utilisateur_wikini.js | 52 + .../form-edit-template/form-edit-template.js | 531 ++++++ .../forms/_forms_form_js_include.twig | 5 +- tools/bazar/templates/forms/forms_form.twig | 3 - .../templates/inputs/conditions-checking.twig | 2 +- 39 files changed, 1982 insertions(+), 1775 deletions(-) rename tools/bazar/presentation/javascripts/{conditionschecking => bazar-fields}/conditionschecking.js (100%) delete mode 100644 tools/bazar/presentation/javascripts/calcfield/form-edit-template.js delete mode 100644 tools/bazar/presentation/javascripts/conditionschecking/form-edit-template.js delete mode 100644 tools/bazar/presentation/javascripts/form-edit-template.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/README create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/acls.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/bookmarklet.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/calc.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/checkbox-group.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/collaborative_doc.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/commons/attributes.js rename tools/bazar/presentation/javascripts/{form-edit-template-helper.js => form-edit-template/fields/commons/render-helper.js} (99%) create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/conditionschecking.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/custom.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/date.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/file.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/hidden.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/image.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/inscriptionliste.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/labelhtml.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/map.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/metadatas.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/radio-group.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/reactions.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/select.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/tabchange.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/tabs.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/tags.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/text.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/titre.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/url.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/fields/utilisateur_wikini.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js diff --git a/tools/bazar/presentation/javascripts/conditionschecking/conditionschecking.js b/tools/bazar/presentation/javascripts/bazar-fields/conditionschecking.js similarity index 100% rename from tools/bazar/presentation/javascripts/conditionschecking/conditionschecking.js rename to tools/bazar/presentation/javascripts/bazar-fields/conditionschecking.js diff --git a/tools/bazar/presentation/javascripts/calcfield/form-edit-template.js b/tools/bazar/presentation/javascripts/calcfield/form-edit-template.js deleted file mode 100644 index f97a59e19..000000000 --- a/tools/bazar/presentation/javascripts/calcfield/form-edit-template.js +++ /dev/null @@ -1,62 +0,0 @@ -typeUserAttrs = { - ...typeUserAttrs, - ...{ - calc: { - displaytext: { - label: _t('BAZ_FORM_EDIT_DISPLAYTEXT_LABEL'), - value: '', - placeholder: '{value}' - }, - formula: { - label: _t('BAZ_FORM_EDIT_FORMULA_LABEL'), - value: '' - }, - read: readConf - // write: writeconf - } - } -} - -templates = { - ...templates, - ...{ - calc(field) { - return { - field: '', - onRender() { - templateHelper.prependHint(field, _t('BAZ_FORM_CALC_HINT', { '\\n': '
' })) - templateHelper.defineLabelHintForGroup(field, 'displaytext', _t('BAZ_FORM_EDIT_DISPLAYTEXT_HELP')) - } - } - } - } -} - -yesWikiMapping = { - ...yesWikiMapping, - ...{ - calc: { - ...defaultMapping, - ...{ - 4: 'displaytext', - 5: 'formula', - 8: '', - 9: '' - } - } - } -} - -typeUserDisabledAttrs = { - ...typeUserDisabledAttrs, - ...{ calc: ['required', 'value', 'default'] } -} - -typeUserEvents.calc = { onclone: copyMultipleSelectValues } - -fields.push({ - label: _t('BAZ_FORM_EDIT_CALC_LABEL'), - name: 'calc', - attrs: { type: 'calc' }, - icon: '' -}) diff --git a/tools/bazar/presentation/javascripts/conditionschecking/form-edit-template.js b/tools/bazar/presentation/javascripts/conditionschecking/form-edit-template.js deleted file mode 100644 index 0bda4de6c..000000000 --- a/tools/bazar/presentation/javascripts/conditionschecking/form-edit-template.js +++ /dev/null @@ -1,80 +0,0 @@ -typeUserAttrs = { - ...typeUserAttrs, - ...{ - conditionschecking: { - condition: { - label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL'), - value: '' - }, - clean: { - label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_CLEAN_LABEL'), - options: { - ' ': _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_CLEAN_OPTION'), - noclean: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_NOCLEAN_OPTION') - } - } - } - } -} - -templates = { - ...templates, - ...{ - conditionschecking(field) { - return { - field: '', - onRender() { - templateHelper.prependHint(field, _t('BAZ_FORM_CONDITIONSCHEKING_HINT', { '\\n': '
' })) - templateHelper.defineLabelHintForGroup(field, 'noclean', _t('BAZ_FORM_CONDITIONSCHEKING_NOCLEAN_HINT')) - } - } - } - } -} - -yesWikiMapping = { - ...yesWikiMapping, - ...{ - conditionschecking: { - ...defaultMapping, - ...{ - 1: 'condition', - 2: 'clean', - 5: '', - 8: '', - 9: '' - } - } - } -} - -typeUserDisabledAttrs = { - ...typeUserDisabledAttrs, - ...{ conditionschecking: ['required', 'value', 'name', 'label'] } -} - -inputSets.push( - { - label: _t('BAZ_FORM_EDIT_CONDITIONCHECKING_LABEL'), - name: 'conditionschecking', - icon: '', - fields: [ - { - type: 'conditionschecking', - label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL') - }, - { - type: 'labelhtml', - label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_END'), - content_saisie: `
` - } - ] - } -) - -fields.push({ - label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL'), - name: 'conditionschecking', - attrs: { type: 'conditionschecking' }, - icon: '' -}) diff --git a/tools/bazar/presentation/javascripts/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template.js deleted file mode 100644 index 25c63d5e2..000000000 --- a/tools/bazar/presentation/javascripts/form-edit-template.js +++ /dev/null @@ -1,1624 +0,0 @@ -const $formBuilderTextInput = $('#form-builder-text') -const $formBuilderContainer = $('#form-builder-container') -let formBuilder - -// When user add manuall via wikiCode a list or a formId that does not exist, keep the value -// so it can be added the select option list -const listAndFormUserValues = {} -// Fill the listAndFormUserValues -const text = $formBuilderTextInput.val().trim() -const textFields = text.split('\n') -for (var i = 0; i < textFields.length; i++) { - const textField = textFields[i] - const fieldValues = textField.split('***') - if (fieldValues.length > 1) { - const wikiType = fieldValues[0] - if ( - [ - 'checkboxfiche', - 'checkbox', - 'liste', - 'radio', - 'listefiche', - 'radiofiche' - ].indexOf(wikiType) > -1 - && fieldValues[1] - && !(fieldValues[1] in formAndListIds) - ) { - listAndFormUserValues[fieldValues[1]] = fieldValues[1] - } - } -} -// Custom fields to add to form builder -const fields = [ - // { - // label: "Sélecteur de date", - // name: "jour", - // attrs: { type: "date" }, - // icon: '', - // }, - { - label: _t('BAZ_FORM_EDIT_TEXT_LABEL'), - name: 'text', - attrs: { type: 'text' }, - icon: - '' - }, - { - label: _t('BAZ_FORM_EDIT_URL_LABEL'), - name: 'url', - attrs: { type: 'url' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_GEO_LABEL'), - name: 'map', - attrs: { type: 'map' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_IMAGE_LABEL'), - name: 'image', - attrs: { type: 'image' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_EMAIL_LABEL'), - name: 'champs_mail', - attrs: { type: 'champs_mail' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_TAGS_LABEL'), - name: 'tags', - attrs: { type: 'tags' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_SUBSCRIBE_LIST_LABEL'), - name: 'inscriptionliste', - attrs: { type: 'inscriptionliste' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_CUSTOM_HTML_LABEL'), - name: 'labelhtml', - attrs: { type: 'labelhtml' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_ACL_LABEL'), - name: 'acls', - attrs: { type: 'acls' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_METADATA_LABEL'), - name: 'metadatas', - attrs: { type: 'metadatas' }, - icon: '' - }, - { - label: 'Bookmarklet', - name: 'bookmarklet', - attrs: { type: 'bookmarklet' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_LINKEDENTRIES_LABEL'), - name: 'listefichesliees', - attrs: { type: 'listefichesliees' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_USERS_WIKINI_LABEL'), - name: 'utilisateur_wikini', - attrs: { type: 'utilisateur_wikini' }, - icon: '' - }, - { - name: 'collaborative_doc', - attrs: { type: 'collaborative_doc' } - }, - { - label: _t('BAZ_FORM_EDIT_TITLE_LABEL'), - name: 'titre', - attrs: { type: 'titre' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_CUSTOM_LABEL'), - name: 'custom', - attrs: { type: 'custom' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_TABS'), - name: 'tabs', - attrs: { type: 'tabs' }, - icon: '' - }, - { - label: _t('BAZ_FORM_EDIT_TABCHANGE'), - name: 'tabchange', - attrs: { type: 'tabchange' }, - icon: '' - }, - { - label: _t('BAZ_REACTIONS_FIELD'), - name: 'reactions', - attrs: { type: 'reactions' }, - icon: '' - } -] - -// Some attributes configuration used in multiple fields -const visibilityOptions = { - ' * ': _t('EVERYONE'), - ' + ': _t('IDENTIFIED_USERS'), - ' % ': _t('BAZ_FORM_EDIT_OWNER_AND_ADMINS'), - '@admins': _t('MEMBER_OF_GROUP', { groupName: 'admin' }) -} -// create list of groups -const formattedGroupList = [] -if (groupsList && groupsList.length > 0) { - const groupsListLen = groupsList.length - for (i = 0; i < groupsListLen; ++i) { - if (groupsList[i] !== 'admins') { - formattedGroupList[`@${groupsList[i]}`] = _t('MEMBER_OF_GROUP', { groupName: groupsList[i] }) - } - } -} - -const aclsOptions = { - ...visibilityOptions, - ...{ - user: - _t('BAZ_FORM_EDIT_USER') - }, - ...formattedGroupList -} -const aclsCommentOptions = { - ...{ 'comments-closed': _t('BAZ_FORM_EDIT_COMMENTS_CLOSED') }, - ...visibilityOptions, - ...{ user: _t('BAZ_FORM_EDIT_USER') }, - ...formattedGroupList -} -const readConf = { label: _t('BAZ_FORM_EDIT_CAN_BE_READ_BY'), options: { ...visibilityOptions, ...formattedGroupList }, multiple: true } -const writeconf = { label: _t('BAZ_FORM_EDIT_CAN_BE_WRITTEN_BY'), options: { ...visibilityOptions, ...formattedGroupList }, multiple: true } -const searchableConf = { - label: _t('BAZ_FORM_EDIT_SEARCH_LABEL'), - options: { '': _t('NO'), 1: _t('YES') } -} -const semanticConf = { - label: _t('BAZ_FORM_EDIT_SEMANTIC_LABEL'), - value: '', - placeholder: 'Ex: https://schema.org/name' -} -const selectConf = { - subtype2: { - label: _t('BAZ_FORM_EDIT_SELECT_SUBTYPE2_LABEL'), - options: { - list: _t('BAZ_FORM_EDIT_SELECT_SUBTYPE2_LIST'), - form: _t('BAZ_FORM_EDIT_SELECT_SUBTYPE2_FORM') - } - }, - listeOrFormId: { - label: _t('BAZ_FORM_EDIT_SELECT_LIST_FORM_ID'), - options: { - ...{ '': '' }, - ...formAndListIds.lists, - ...formAndListIds.forms, - ...listAndFormUserValues - } - }, - listId: { - label: '', - options: { ...formAndListIds.lists, ...listAndFormUserValues } - }, - formId: { - label: '', - options: { ...formAndListIds.forms, ...listAndFormUserValues } - }, - defaultValue: { - label: _t('BAZ_FORM_EDIT_SELECT_DEFAULT'), - value: '' - }, - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - read: readConf, - write: writeconf, - semantic: semanticConf - // searchable: searchableConf -> 10/19 Florian say that this conf is not working for now -} -const TabsConf = { - formTitles: { - label: _t('BAZ_FORM_EDIT_TABS_FOR_FORM'), - value: _t('BAZ_FORM_EDIT_TABS_FORMTITLES_VALUE'), - placeholder: _t('BAZ_FORM_EDIT_TABS_FORMTITLES_DESCRIPTION'), - description: _t('BAZ_FORM_EDIT_TABS_FORMTITLES_DESCRIPTION') - }, - viewTitles: { - label: _t('BAZ_FORM_EDIT_TABS_FOR_ENTRY'), - value: '', - placeholder: _t('BAZ_FORM_EDIT_TABS_VIEWTITLES_DESCRIPTION'), - description: _t('BAZ_FORM_EDIT_TABS_VIEWTITLES_DESCRIPTION') - }, - moveSubmitButtonToLastTab: { - label: _t('BAZ_FORM_EDIT_TABS_MOVESUBMITBUTTONTOLASTTAB_LABEL'), - options: { '': _t('NO'), moveSubmit: _t('YES') }, - description: _t('BAZ_FORM_EDIT_TABS_MOVESUBMITBUTTONTOLASTTAB_DESCRIPTION') - }, - btnColor: { - label: _t('BAZ_FORM_EDIT_TABS_BTNCOLOR_LABEL'), - options: { 'btn-primary': _t('PRIMARY'), 'btn-secondary-1': `${_t('SECONDARY')} 1`, 'btn-secondary-2': `${_t('SECONDARY')} 2` } - }, - btnSize: { - label: _t('BAZ_FORM_EDIT_TABS_BTNSIZE_LABEL'), - options: { '': _t('NORMAL_F'), 'btn-xs': _t('SMALL_F') } - } -} -const TabChangeConf = { - formChange: { - label: _t('BAZ_FORM_EDIT_TABS_FOR_FORM'), - options: { formChange: _t('YES'), noformchange: _t('NO') }, - description: `${_t('BAZ_FORM_EDIT_TABCHANGE_CHANGE_LABEL')} ${_t('BAZ_FORM_EDIT_TABS_FOR_FORM')}` - }, - viewChange: { - label: _t('BAZ_FORM_EDIT_TABS_FOR_ENTRY'), - options: { '': _t('NO'), viewChange: _t('YES') }, - description: `${_t('BAZ_FORM_EDIT_TABCHANGE_CHANGE_LABEL')} ${_t('BAZ_FORM_EDIT_TABS_FOR_ENTRY')}` - } -} - -// Attributes to be configured for each field -let typeUserAttrs = { - text: { - size: { label: _t('BAZ_FORM_EDIT_TEXT_SIZE'), value: '' }, - maxlength: { label: _t('BAZ_FORM_EDIT_TEXT_MAX_LENGTH'), value: '' }, - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - separator: { label: '' }, // separate important attrs from others - subtype: { - label: _t('BAZ_FORM_EDIT_TEXT_TYPE_LABEL'), - options: { - text: _t('BAZ_FORM_EDIT_TEXT_TYPE_TEXT'), - number: _t('BAZ_FORM_EDIT_TEXT_TYPE_NUMBER'), - range: _t('BAZ_FORM_EDIT_TEXT_TYPE_RANGE'), - url: _t('BAZ_FORM_EDIT_TEXT_TYPE_URL'), - password: _t('BAZ_FORM_EDIT_TEXT_TYPE_PASSWORD'), - color: _t('BAZ_FORM_EDIT_TEXT_TYPE_COLOR') - } - }, - read: readConf, - write: writeconf, - semantic: semanticConf, - pattern: { - label: _t('BAZ_FORM_EDIT_TEXT_PATTERN'), - value: '', - placeholder: `${_t('BAZ_FORM_EDIT_ADVANCED_MODE')} Ex: [0-9]+ ou [A-Za-z]{3}, ...` - } - }, - url: { - read: readConf, - write: writeconf, - semantic: semanticConf - }, - champs_mail: { - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - separator: { label: '' }, // separate important attrs from others - send_form_content_to_this_email: { - label: _t('BAZ_FORM_EDIT_EMAIL_SEND_FORM_CONTENT_LABEL'), - options: { 0: _t('NO'), 1: _t('YES') } - }, - replace_email_by_button: { - label: _t('BAZ_FORM_EDIT_EMAIL_REPLACE_BY_BUTTON_LABEL'), - options: { '': _t('NO'), form: _t('YES') }, - value: 'form' - }, - seeEmailAcls: { ...readConf, ...{ label: _t('BAZ_FORM_EDIT_EMAIL_SEE_MAIL_ACLS') } }, - readWhenForm: { ...readConf, ...{ label: _t('BAZ_FORM_EDIT_EMAIL_SEND_ACLS') } }, - // searchable: searchableConf, -> 10/19 Florian say that this conf is not working for now - read: readConf, - write: writeconf, - semantic: semanticConf - }, - map: { - name_latitude: { label: _t('BAZ_FORM_EDIT_MAP_LATITUDE'), value: 'bf_latitude' }, - name_longitude: { label: _t('BAZ_FORM_EDIT_MAP_LONGITUDE'), value: 'bf_longitude' }, - autocomplete_street: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET_PLACEHOLDER') }, - autocomplete_postalcode: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_POSTALCODE'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_POSTALCODE_PLACEHOLDER') }, - autocomplete_town: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_TOWN'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_TOWN_PLACEHOLDER') }, - autocomplete_county: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_COUNTY'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_COUNTY_PLACEHOLDER') }, - autocomplete_state: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STATE'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STATE_PLACEHOLDER') }, - autocomplete_other: { label: '', value: '' }, - autocomplete_street1: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET1'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET1_PLACEHOLDER') }, - autocomplete_street2: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET2'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET2_PLACEHOLDER') }, - geolocate: { - label: _t('BAZ_FORM_EDIT_GEOLOCATE'), - options: { 0: _t('NO'), 1: _t('YES') } - } - }, - date: { - today_button: { - label: _t('BAZ_FORM_EDIT_DATE_TODAY_BUTTON'), - options: { ' ': _t('NO'), today: _t('YES') } - }, - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - read: readConf, - write: writeconf, - semantic: semanticConf - }, - image: { - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - thumb_height: { label: _t('BAZ_FORM_EDIT_IMAGE_HEIGHT'), value: '300' }, - thumb_width: { label: _t('BAZ_FORM_EDIT_IMAGE_WIDTH'), value: '400' }, - resize_height: { label: _t('BAZ_FORM_EDIT_IMAGE_HEIGHT_RESIZE'), value: '600' }, - resize_width: { label: _t('BAZ_FORM_EDIT_IMAGE_WIDTH_RESIZE'), value: '800' }, - align: { - label: _t('BAZ_FORM_EDIT_IMAGE_ALIGN_LABEL'), - value: 'right', - options: { left: _t('LEFT'), right: _t('RIGHT') } - }, - read: readConf, - write: writeconf, - semantic: semanticConf - }, - select: { - ...selectConf, - ...{ - queries: { - label: _t('BAZ_FORM_EDIT_QUERIES_LABEL'), - value: '', - placeholder: 'ex. : checkboxfiche6=PageTag ; cf. https://yeswiki.net/?LierFormulairesEntreEux' - } - } - }, - 'checkbox-group': { - ...selectConf, - ...{ - fillingMode: { - label: _t('BAZ_FORM_EDIT_FILLING_MODE_LABEL'), - options: { - ' ': _t('BAZ_FORM_EDIT_FILLING_MODE_NORMAL'), - tags: _t('BAZ_FORM_EDIT_FILLING_MODE_TAGS'), - dragndrop: _t('BAZ_FORM_EDIT_FILLING_MODE_DRAG_AND_DROP') - } - }, - queries: { - label: _t('BAZ_FORM_EDIT_QUERIES_LABEL'), - value: '', - placeholder: 'ex. : checkboxfiche6=PageTag ; cf. https://yeswiki.net/?LierFormulairesEntreEux' - } - } - }, - 'radio-group': { - ...selectConf, - ...{ - fillingMode: { - label: _t('BAZ_FORM_EDIT_FILLING_MODE_LABEL'), - options: { - ' ': _t('BAZ_FORM_EDIT_FILLING_MODE_NORMAL'), - tags: _t('BAZ_FORM_EDIT_FILLING_MODE_TAGS') - } - }, - queries: { - label: _t('BAZ_FORM_EDIT_QUERIES_LABEL'), - value: '', - placeholder: 'ex. : checkboxfiche6=PageTag ; cf. https://yeswiki.net/?LierFormulairesEntreEux' - } - } - }, - textarea: { - syntax: { - label: _t('BAZ_FORM_EDIT_TEXTAREA_SYNTAX_LABEL'), - options: { - wiki: 'Wiki', - html: _t('BAZ_FORM_EDIT_TEXTAREA_SYNTAX_HTML'), - nohtml: _t('BAZ_FORM_EDIT_TEXTAREA_SYNTAX_NOHTML') - } - }, - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - size: { label: _t('BAZ_FORM_EDIT_TEXTAREA_SIZE_LABEL'), value: '' }, - rows: { - label: _t('BAZ_FORM_EDIT_TEXTAREA_ROWS_LABEL'), - type: 'number', - placeholder: _t('BAZ_FORM_EDIT_TEXTAREA_ROWS_PLACEHOLDER') - }, - read: readConf, - write: writeconf, - semantic: semanticConf - }, - file: { - readlabel: { - label: _t('BAZ_FORM_EDIT_FILE_READLABEL_LABEL'), - value: '', - placeholder: _t('BAZ_FILEFIELD_FILE') - }, - maxsize: { label: _t('BAZ_FORM_EDIT_FILE_MAXSIZE_LABEL'), value: '' }, - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - read: readConf, - write: writeconf, - semantic: semanticConf - }, - tags: { - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - read: readConf, - write: writeconf, - semantic: semanticConf - }, - inscriptionliste: { - subscription_email: { label: _t('BAZ_FORM_EDIT_INSCRIPTIONLISTE_EMAIL_LABEL'), value: '' }, - email_field_id: { - label: _t('BAZ_FORM_EDIT_INSCRIPTIONLISTE_EMAIL_FIELDID'), - value: 'bf_mail' - }, - mailing_list_tool: { - label: _t('BAZ_FORM_EDIT_INSCRIPTIONLISTE_MAILINGLIST'), - value: '' - } - }, - labelhtml: { - label: { value: _t('BAZ_FORM_EDIT_CUSTOM_HTML_LABEL'), value: '' }, - content_saisie: { label: _t('BAZ_FORM_EDIT_EDIT_CONTENT_LABEL'), type: 'textarea', rows: '4', value: '' }, - content_display: { label: _t('BAZ_FORM_EDIT_VIEW_CONTENT_LABEL'), type: 'textarea', rows: '4', value: '' } - }, - utilisateur_wikini: { - name_field: { label: _t('BAZ_FORM_EDIT_USERS_WIKINI_NAME_FIELD_LABEL'), value: 'bf_titre' }, - email_field: { - label: _t('BAZ_FORM_EDIT_USERS_WIKINI_EMAIL_FIELD_LABEL'), - value: 'bf_mail' - }, - // mailing_list: { - // label: "Inscrite à une liste de diffusion" - // }, - autoupdate_email: { - label: _t('BAZ_FORM_EDIT_USERS_WIKINI_AUTOUPDATE_MAIL'), - options: { 0: _t('NO'), 1: _t('YES') } - }, - auto_add_to_group: { - label: _t('BAZ_FORM_EDIT_ADD_TO_GROUP_LABEL'), - value: '', - placeholder: _t('BAZ_FORM_EDIT_ADD_TO_GROUP_DESCRIPTION'), - description: _t('BAZ_FORM_EDIT_ADD_TO_GROUP_DESCRIPTION') - } - }, - acls: { - read: { label: _t('BAZ_FORM_EDIT_ACL_READ_LABEL'), options: aclsOptions, multiple: true }, - write: { label: _t('BAZ_FORM_EDIT_ACL_WRITE_LABEL'), options: aclsOptions, multiple: true }, - comment: { label: _t('BAZ_FORM_EDIT_ACL_COMMENT_LABEL'), options: aclsCommentOptions, multiple: true }, - askIfActivateComments: { - label: _t('BAZ_FORM_EDIT_ACL_ASK_IF_ACTIVATE_COMMENT_LABEL'), - options: { 0: _t('NO'), 1: _t('YES') } - }, - fieldLabel: { - label: _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_LABEL'), - value: '', - placeholder: _t('BAZ_ACTIVATE_COMMENTS') - }, - hint: { - label: _t('BAZ_FORM_EDIT_HELP'), - value: '', - placeholder: _t('BAZ_ACTIVATE_COMMENTS_HINT') - }, - value: { - label: _t('BAZ_FORM_EDIT_COMMENTS_FIELD_DEFAULT_ACTIVATION_LABEL'), - options: { non: _t('NO'), oui: _t('YES'), ' ': '' } - } - }, - metadatas: { - theme: { - label: _t('BAZ_FORM_EDIT_METADATA_THEME_LABEL'), - value: '', - placeholder: 'margot, interface, colibris' - }, - squelette: { label: _t('BAZ_FORM_EDIT_METADATA_SQUELETON_LABEL'), value: '1col.tpl.html' }, - style: { label: _t('BAZ_FORM_EDIT_METADATA_STYLE_LABEL'), value: '', placeholder: 'bootstrap.css...' }, - preset: { label: _t('BAZ_FORM_EDIT_METADATA_PRESET_LABEL'), value: '', placeholder: `blue.css (${_t('BAZ_FORM_EDIT_METADATA_PRESET_PLACEHOLDER')})` }, - image: { label: _t('BAZ_FORM_EDIT_METADATA_BACKGROUND_IMAGE_LABEL'), value: '', placeholder: 'foret.jpg...' } - }, - bookmarklet: { - urlField: { label: _t('BAZ_FORM_EDIT_BOOKMARKLET_URLFIELD_LABEL'), value: 'bf_url' }, - descriptionField: { label: _t('BAZ_FORM_EDIT_BOOKMARKLET_DESCRIPTIONFIELD_LABEL'), value: 'bf_description' }, - hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: _t('BAZ_FORM_EDIT_BOOKMARKLET_HINT_DEFAULT_VALUE') }, - text: { label: _t('BAZ_FORM_EDIT_BOOKMARKLET_TEXT_LABEL'), value: _t('BAZ_FORM_EDIT_BOOKMARKLET_TEXT_VALUE') } - }, - collaborative_doc: {}, - titre: {}, - listefichesliees: { - id: { label: _t('BAZ_FORM_EDIT_LISTEFICHES_FORMID_LABEL'), value: '' }, - query: { - label: _t('BAZ_FORM_EDIT_LISTEFICHES_QUERY_LABEL'), - value: '', - placeholder: _t('BAZ_FORM_EDIT_LISTEFICHES_QUERY_PLACEHOLDER', { url: 'https://yeswiki.net/?DocQuery/iframe' }) - }, - param: { - label: _t('BAZ_FORM_EDIT_LISTEFICHES_PARAMS_LABEL'), - value: '', - placeholder: 'Ex: champs="bf_nom" ordre="desc"' - }, - number: { label: _t('BAZ_FORM_EDIT_LISTEFICHES_NUMBER_LABEL'), value: '', placeholder: '' }, - template: { - label: _t('BAZ_FORM_EDIT_LISTEFICHES_TEMPLATE_LABEL'), - value: '', - placeholder: - _t('BAZ_FORM_EDIT_LISTEFICHES_TEMPLATE_PLACEHOLDER') - }, - type_link: { - label: _t('BAZ_FORM_EDIT_LISTEFICHES_LISTTYPE_LABEL'), - value: '', - placeholder: - _t('BAZ_FORM_EDIT_LISTEFICHES_LISTTYPE_PLACEHOLDER') - }, - read: readConf, - write: writeconf, - semantic: semanticConf - }, - reactions: { - fieldlabel: { - label: _t('BAZ_REACTIONS_FIELD_ACTIVATE_LABEL'), - value: '', - placeholder: _t('BAZ_ACTIVATE_REACTIONS') - }, - value: { - label: _t('BAZ_REACTIONS_FIELD_DEFAULT_ACTIVATION_LABEL'), - options: { oui: _t('YES'), non: _t('NO') } - }, - labels: { - label: _t('BAZ_REACTIONS_FIELD_LABELS_LABEL'), - value: '' - }, - images: { - label: _t('BAZ_REACTIONS_FIELD_IMAGES_LABEL'), - value: '', - placeholder: _t('BAZ_REACTIONS_FIELD_IMAGES_PLACEHOLDER') - }, - ids: { - label: _t('BAZ_REACTIONS_FIELD_IDS_LABEL'), - value: '' - }, - read: readConf, - write: writeconf, - semantic: semanticConf - }, - custom: { - param0: { label: 'Param0', value: '' }, - param1: { label: 'Param1', value: '' }, - param2: { label: 'Param2', value: '' }, - param3: { label: 'Param3', value: '' }, - param4: { label: 'Param4', value: '' }, - param5: { label: 'Param5', value: '' }, - param6: { label: 'Param6', value: '' }, - param7: { label: 'Param7', value: '' }, - param8: { label: 'Param8', value: '' }, - param9: { label: 'Param9', value: '' }, - param10: { label: 'Param10', value: '' }, - param11: { label: 'Param11', value: '' }, - param12: { label: 'Param12', value: '' }, - param13: { label: 'Param13', value: '' }, - param14: { label: 'Param14', value: '' }, - param15: { label: 'Param15', value: '' } - }, - tabs: TabsConf, - tabchange: TabChangeConf -} - -// How a field is represented in the formBuilder view -let templates = { - champs_mail(fieldData) { - return { - field: ``, - onRender() { - const currentField = templateHelper.getHolder(fieldData).parent() - templateHelper.initializeField(currentField) - const arrayEquals = (a, b) => { - if (a.length != b.length) { - return false - } - return (a.every((e) => b.includes(e)) && b.every((e) => a.includes(e))) - } - currentField.find('select[name=read]:not(.initialized)') - .on('change', (event) => { - const element = event.target - const base = $(element).closest('.champs_mail-field.form-field') - $(element).addClass('initialized') - - const readWhenFormInput = $(base).find('select[name=readWhenForm]') - if (readWhenFormInput && readWhenFormInput.length > 0 && !arrayEquals(readWhenFormInput.val(), $(element).val())) { - readWhenFormInput.val($(element).val()) - } - }).trigger('change') - currentField.find('select[name=readWhenForm]:not(.initialized)') - .on('change', (event) => { - const element = event.target - const base = $(element).closest('.champs_mail-field.form-field') - $(element).addClass('initialized') - - const readInput = $(base).find('select[name=read]') - if (readInput && readInput.length > 0 && !arrayEquals(readInput.val(), $(element).val())) { - readInput.val($(element).val()) - } - }).trigger('change') - currentField - .find('select[name=replace_email_by_button]:not(.initialized)') - .on('change', (event) => { - const element = event.target - - const base = $(element).closest('.champs_mail-field.form-field') - $(element).addClass('initialized') - - const setDisplay = (base, name, newValue) => { - const wrapper = $(base).find(`div.form-group.${name}-wrap`) - if (wrapper && wrapper.length > 0) { - if (newValue) { - wrapper.show() - } else { - wrapper.hide() - } - } - } - if ($(element).val() == 'form') { - // when chosing 'form' (or at init), if readAcl is ' % ', prefer ' * ' - // to show button to everyone - const field = currentField.find('select[name=read]') - if (arrayEquals(field.val(), [' % '])) { - field.val([' * ']) - field.trigger('change') - } - setDisplay(base, 'readWhenForm', 1) - setDisplay(base, 'seeEmailAcls', 1) - setDisplay(base, 'read', 0) - } else { - // when chosing 'text' (or at init), if readAcl is ' * ', prefer ' % ' - // to force email not to be shown - const field = currentField.find('select[name=read]') - if (arrayEquals(field.val(), [' * ']) && !currentField.find('select[name=write]').val().includes(' * ')) { - field.val([' % ']) - field.trigger('change') - } - setDisplay(base, 'readWhenForm', 0) - setDisplay(base, 'seeEmailAcls', 0) - setDisplay(base, 'read', 1) - } - }) - .trigger('change') - } - } - }, - map(fieldData) { - return { - field: _t('BAZ_FORM_EDIT_MAP_FIELD'), - onRender() { - const toggleState = function(name, state) { - const formGroup = templateHelper.getFormGroup(fieldData, name) - if (formGroup !== null) { - if (state === 'show') { - formGroup.show() - } else { - formGroup.hide() - } - } - } - const toggleStates = function(state) { - ['autocomplete_street1', 'autocomplete_street2'].forEach((name) => toggleState(name, state)) - } - // initMapAutocompleteUpdate() - $('.map-field.form-field') - .find('input[type=text][name=autocomplete_street]:not(.initialized)' - + ',input[type=text][name=autocomplete_street1]:not(.initialized)' - + ',input[type=text][name=autocomplete_street2]:not(.initialized)' - + ',input[type=text][name=autocomplete_county]:not(.initialized)' - + ',input[type=text][name=autocomplete_state]:not(.initialized)' - + ',select[name=geolocate]:not(.initialized)') - .on('change', (event) => { - // mapAutocompleteUpdate(event.target) - const element = event.target - const base = $(element).closest('.map-field.form-field') - if (!$(element).hasClass('initialized')) { - $(element).addClass('initialized') - if ($(element).val().length == 0 || $(element).prop('tagName') === 'SELECT') { - // mapAutocompleteUpdateExtractFromOther(base) - const other = { - geolocate: '', - street: '', - street1: '', - street2: '', - county: '', - state: '' - } - const autoCompleteOther = $(base) - .find('input[type=text][name=autocomplete_other]') - .first() - if (autoCompleteOther && autoCompleteOther.length > 0) { - const value = autoCompleteOther.val().split('|') - other.geolocate = ['1', 1, true].includes(value[0]) ? '1' : '0' - other.street = value[1] || '' - other.street1 = value[2] || '' - other.street2 = value[3] || '' - other.county = value[4] || '' - other.state = value[5] || '' - } - switch (element.getAttribute('name')) { - case 'autocomplete_street': - $(element).val(other.street) - break - case 'autocomplete_street1': - $(element).val(other.street1) - break - case 'autocomplete_street2': - $(element).val(other.street2) - break - case 'autocomplete_county': - $(element).val(other.county) - break - case 'autocomplete_state': - $(element).val(other.state) - break - case 'geolocate': - $(element).val(other.geolocate === '1' ? '1' : '0') - break - default: - break - } - } - } else { - // autocompleteUpdateSaveToOther(base) - const autoCompleteOther = $(base) - .find('input[type=text][name=autocomplete_other]') - .first() - if (autoCompleteOther && autoCompleteOther.length > 0) { - const results = { - geolocate: '', - street: '', - street1: '', - street2: '', - county: '', - state: '' - } - const associations = { - street: 'autocomplete_street', - street1: 'autocomplete_street1', - street2: 'autocomplete_street2', - county: 'autocomplete_county', - state: 'autocomplete_state' - } - for (const key in associations) { - const autoCompleteField = $(base) - .find(`input[type=text][name=${associations[key]}]`) - .first() - if (autoCompleteField && autoCompleteField.length > 0) { - results[key] = autoCompleteField.val() || '' - } - } - // geolocate - const geolocateField = $(base) - .find('select[name=geolocate]') - .first() - if (geolocateField && geolocateField.length > 0) { - results.geolocate = geolocateField.val() || '' - } - autoCompleteOther.val( - `${results.geolocate - }|${results.street}` - + `|${results.street1}` - + `|${results.street2}` - + `|${results.county}` - + `|${results.state}` - ) - } - } - }) - .trigger('change') - - templateHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street', ` -
- ${_t('GEOLOCATER_GROUP_GEOLOCATIZATION')} -
${_t('GEOLOCATER_GROUP_GEOLOCATIZATION_HINT')}
-
- `) - $advancedParams = $(` -
- -
- -
-
- `) - templateHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street1', $advancedParams) - $advancedParams.find('button').on('click', function(event) { - if ($(this).hasClass('opened')) { - $(this).removeClass('opened') - $(this).html(_t('GEOLOCATER_SEE_ADVANCED_PARAMS')) - toggleStates('hide') - } else { - $(this).addClass('opened') - $(this).html(_t('GEOLOCATER_HIDE_ADVANCED_PARAMS')) - toggleStates('show') - } - event.preventDefault() - event.stopPropagation() - }) - toggleStates('hide') - templateHelper.prependHTMLBeforeGroup(fieldData, 'geolocate', '

') - } - } - }, - image(fieldData) { - return { field: '' } - }, - text(fieldData) { - let string = `` - else if (fieldData.subtype == 'range' || fieldData.subtype == 'number') string += ` min="${fieldData.size || ''}" max="${fieldData.maxlength || ''}"/>` - else { string += ` value="${fieldData.value}"/>` } - return { field: string } - }, - url(fieldData) { - return { field: `` } - }, - tags(fieldData) { - return { field: '' } - }, - inscriptionliste(field) { - return { field: '' } - }, - labelhtml(field) { - return { - field: - `
${field.content_saisie || ''}
-
${field.content_display || ''}
` - } - }, - utilisateur_wikini(field) { - return { - field: '', - onRender() { - templateHelper.defineLabelHintForGroup(field, 'auto_add_to_group', _t('BAZ_FORM_EDIT_ADD_TO_GROUP_HELP')) - } - } - }, - acls(field) { - return { - field: field.askIfActivateComments == 1 ? ` ${field.fieldlabel || _t('BAZ_ACTIVATE_COMMENTS')}` : '', - onRender() { - const currentField = templateHelper.getHolder(field).parent() - templateHelper.initializeField(currentField) - $(currentField) - .find('select[name=askIfActivateComments]:not(.initialized)') - .change((event) => { - const element = event.target - - const base = $(element).closest('.acls-field.form-field') - $(element).addClass('initialized') - - const nameInput = $(base).find('input[type=text][name=name]') - if (nameInput.val().trim().length == 0 - || nameInput.val().trim() == 'bf_acls') { - nameInput.val('bf_commentaires') - } - - const visibleSelect = $(base).find('select[name=askIfActivateComments]') - const selectedValue = visibleSelect.val() - - const subElements = $(base) - .find('.form-group.fieldLabel-wrap,.form-group.hint-wrap,.form-group.name-wrap,.form-group.value-wrap') - if ([1, '1'].includes(selectedValue)) { - subElements.show() - const commentInput = $(base).find('select[name=comment]') - const currentValue = commentInput.val() - if (Array.isArray(currentValue) - && ( - currentValue.length == 0 - || (currentValue.length == 1 && currentValue.includes('comments-closed')) - )) { - commentInput.val([' + ']) - } - } else { - subElements.hide() - } - }) - .trigger('change') - templateHelper.defineLabelHintForGroup(field, 'fieldlabel', _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_HINT')) - templateHelper.defineLabelHintForGroup(field, 'hint', _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_HINT')) - } - } - }, - metadatas(field) { - return { field: '' } - }, - bookmarklet(field) { - return { - field: '', - onRender() { - templateHelper.prependHint(field, _t('BAZ_BOOKMARKLET_HINT', { '\\n': '
' })) - } - } - }, - listefichesliees(field) { - return { field: '' } - }, - collaborative_doc(field) { - return { field: _t('BAZ_FORM_EDIT_COLLABORATIVE_DOC_FIELD') } - }, - titre(field) { - return { field: field.value } - }, - custom(field) { - return { field: '' } - }, - tabs(field) { - return { - field: '', - onRender() { - templateHelper.prependHint(field, _t('BAZ_FORM_TABS_HINT', { - '\\n': '
', - 'tabs-field-label': _t('BAZ_FORM_EDIT_TABS'), - 'tabchange-field-label': _t('BAZ_FORM_EDIT_TABCHANGE') - })) - templateHelper.prependHTMLBeforeGroup(field, 'formTitles', $('
').addClass('form-group').append($('').append(_t('BAZ_FORM_EDIT_TABS_TITLES_LABEL')))) - templateHelper.defineLabelHintForGroup(field, 'formTitles', _t('BAZ_FORM_EDIT_TABS_FORMTITLES_DESCRIPTION')) - templateHelper.defineLabelHintForGroup(field, 'viewTitles', _t('BAZ_FORM_EDIT_TABS_VIEWTITLES_DESCRIPTION')) - templateHelper.prependHTMLBeforeGroup(field, 'moveSubmitButtonToLastTab', $('
').addClass('form-group')) - - const holder = templateHelper.getHolder(field) - if (holder) { - const formGroup = holder.find('.formTitles-wrap') - if (typeof formGroup !== undefined && formGroup.length > 0) { - const input = formGroup.find('input').first() - if (typeof input !== undefined && input.length > 0) { - $(input).val($(input).val().replace(/\|/g, ',')) - } - } - } - } - } - }, - tabchange(field) { - return { - field: '', - onRender() { - templateHelper.prependHint(field, _t('BAZ_FORM_TABS_HINT', { - '\\n': '
', - 'tabs-field-label': _t('BAZ_FORM_EDIT_TABS'), - 'tabchange-field-label': _t('BAZ_FORM_EDIT_TABCHANGE') - })) - templateHelper.prependHTMLBeforeGroup(field, 'formChange', $('
').addClass('form-group').append($('').append(_t('BAZ_FORM_EDIT_TABCHANGE_CHANGE_LABEL')))) - } - } - }, - reactions(field) { - return { - field: ` ${field.fieldlabel || _t('BAZ_ACTIVATE_REACTIONS')}`, - onRender() { - templateHelper.defineLabelHintForGroup(field, 'fieldlabel', _t('BAZ_REACTIONS_FIELD_ACTIVATE_HINT')) - templateHelper.defineLabelHintForGroup(field, 'ids', _t('BAZ_REACTIONS_FIELD_IDS_HINT')) - templateHelper.defineLabelHintForGroup(field, 'images', _t('BAZ_REACTIONS_FIELD_IMAGES_HINT')) - templateHelper.defineLabelHintForGroup(field, 'labels', _t('BAZ_REACTIONS_FIELD_LABELS_HINT')) - } - } - } -} - -let typeUserDisabledAttrs = { - tabs: ['required', 'value', 'name', 'label'], - tabchange: ['required', 'value', 'name', 'label'], - bookmarklet: ['required', 'value'], - reactions: ['label', 'required'], - acls: ['label', 'required'] -} - -const inputSets = [ - { - label: _t('BAZ_FORM_EDIT_TABS'), - name: 'tabs', - icon: '', - fields: [ - { - type: 'tabs', - label: _t('BAZ_FORM_EDIT_TABS') - }, - { - type: 'tabchange', - label: _t('BAZ_FORM_EDIT_TABCHANGE') - }, - { - type: 'tabchange', - label: _t('BAZ_FORM_EDIT_TABCHANGE') - }, - { - type: 'tabchange', - label: _t('BAZ_FORM_EDIT_TABCHANGE') - } - ] - } -] - -// Mapping betwwen yes wiki syntax and FormBuilder json syntax -const defaultMapping = { - 0: 'type', - 1: 'name', - 2: 'label', - 3: 'size', - 4: 'maxlength', - 5: 'value', - 6: 'pattern', - 7: 'subtype', - 8: 'required', - 9: 'searchable', - 10: 'hint', - 11: 'read', - 12: 'write', - 14: 'semantic', - 15: 'queries' -} -const lists = { - ...defaultMapping, - ...{ 1: 'listeOrFormId', 5: 'defaultValue', 6: 'name' } -} -let yesWikiMapping = { - text: defaultMapping, - url: defaultMapping, - number: defaultMapping, - champs_mail: { - ...defaultMapping, - ...{ 4: 'seeEmailAcls', 6: 'replace_email_by_button', 9: 'send_form_content_to_this_email' } - }, - map: { - 0: 'type', - 1: 'name_latitude', - 2: 'name_longitude', - 3: '', - 4: 'autocomplete_postalcode', - 5: 'autocomplete_town', - 6: 'autocomplete_other', - 8: 'required' - }, - date: { ...defaultMapping, ...{ 5: 'today_button' } }, - image: { - ...defaultMapping, - ...{ - 1: 'name', - 3: 'thumb_height', - 4: 'thumb_width', - 5: 'resize_height', - 6: 'resize_width', - 7: 'align' - } - }, - select: lists, - 'checkbox-group': { ...lists, ...{ 7: 'fillingMode' } }, - 'radio-group': { ...lists, ...{ 7: 'fillingMode' } }, - textarea: { ...defaultMapping, ...{ 4: 'rows', 7: 'syntax' } }, - file: { ...defaultMapping, ...{ 3: 'maxsize', 6: 'readlabel' } }, - tags: defaultMapping, - inscriptionliste: { - 0: 'type', - 1: 'subscription_email', - 2: 'label', - 3: 'email_field_id', - 4: 'mailing_list_tool' - }, - labelhtml: { 0: 'type', 1: 'content_saisie', 2: '', 3: 'content_display' }, - utilisateur_wikini: { - ...defaultMapping, - ...{ 0: 'type', 1: 'name_field', 2: 'email_field', 5: '', /* 5:"mailing_list", */6: 'auto_add_to_group', 8: '', 9: 'autoupdate_email' } - }, - titre: { 0: 'type', 1: 'value', 2: 'label' }, - acls: { - 0: 'type', - 1: 'read', - 2: 'write', - 3: 'comment', - 4: 'fieldLabel', - 5: 'value', - 6: 'name', - 7: 'askIfActivateComments', - 8: '', - 9: '', - 10: 'hint' - }, - metadatas: { 0: 'type', 1: 'theme', 2: 'squelette', 3: 'style', 4: 'image', 5: 'preset' }, - hidden: { 0: 'type', 1: 'name', 5: 'value' }, - bookmarklet: { - 0: 'type', - 1: 'name', - 2: 'label', - 3: 'urlField', - 4: 'descriptionField', - 5: 'text', - 6: '', - 7: '', - 8: '', - 9: '', - 10: 'hint' - }, - listefichesliees: { - 0: 'type', - 1: 'id', - 2: 'query', - 3: 'param', - 4: 'number', - 5: 'template', - 6: 'type_link' - }, - collaborative_doc: defaultMapping, - custom: { - 0: 'param0', - 1: 'param1', - 2: 'param2', - 3: 'param3', - 4: 'param4', - 5: 'param5', - 6: 'param6', - 7: 'param7', - 8: 'param8', - 9: 'param9', - 10: 'param10', - 11: 'param11', - 12: 'param12', - 13: 'param13', - 14: 'param14', - 15: 'param15' - }, - tabs: { - ...defaultMapping, - ...{ - 1: 'formTitles', - 2: '', - 3: 'viewTitles', - 5: 'moveSubmitButtonToLastTab', - 6: '', - 7: 'btnColor', - 9: 'btnSize' - } - }, - tabchange: { - ...defaultMapping, - ...{ - 1: 'formChange', - 2: '', - 3: 'viewChange' - } - }, - reactions: { - ...defaultMapping, - ...{ - 2: 'ids', - 3: 'labels', - 4: 'images', - 6: 'fieldlabel' - } - } -} -// Mapping betwwen yeswiki field type and standard field implemented by form builder -const yesWikiTypes = { - lien_internet: { type: 'url' }, - lien_internet_bis: { type: 'text', subtype: 'url' }, - mot_de_passe: { type: 'text', subtype: 'password' }, - // "nombre": { type: "text", subtype: "tel" }, - texte: { type: 'text' }, // all other type text subtype (range, text, tel) - textelong: { type: 'textarea', subtype: 'textarea' }, - listedatedeb: { type: 'date' }, - listedatefin: { type: 'date' }, - jour: { type: 'date' }, - map: { type: 'map' }, - carte_google: { type: 'map' }, - checkbox: { type: 'checkbox-group', subtype2: 'list' }, - liste: { type: 'select', subtype2: 'list' }, - radio: { type: 'radio-group', subtype2: 'list' }, - checkboxfiche: { type: 'checkbox-group', subtype2: 'form' }, - listefiche: { type: 'select', subtype2: 'form' }, - radiofiche: { type: 'radio-group', subtype2: 'form' }, - fichier: { type: 'file', subtype: 'file' }, - champs_cache: { type: 'hidden' }, - listefiches: { type: 'listefichesliees' } -} - -const defaultFieldsName = { - textarea: 'bf_description', - image: 'bf_image', - champs_mail: 'bf_mail', - date: 'bf_date_debut_evenement' -} - -const I18nOption = { - ar: 'ar-SA', - ca: 'ca-ES', - cs: 'cs-CZ', - da: 'da-DK', - de: 'de-DE', - el: 'el-GR', - en: 'en-US', - es: 'es-ES', - fa: 'fa-IR', - fi: 'fi-FI', - fr: 'fr-FR', - he: 'he-IL', - hu: 'hu-HU', - it: 'it-IT', - ja: 'ja-JP', - my: 'my-MM', - nb: 'nb-NO', - pl: 'pl-PL', - pt: 'pt-BR', - qz: 'qz-MM', - ro: 'ro-RO', - ru: 'ru-RU', - sj: 'sl-SL', - th: 'th-TH', - uk: 'uk-UA', - vi: 'vi-VN', - zh: 'zh-CN' -} - -function copyMultipleSelectValues(currentField) { - const currentId = $(currentField).prop('id') - // based on formBuilder/Helpers.js 'incrementId' function - const split = currentId.lastIndexOf('-') - const clonedFieldNumber = parseInt(currentId.substring(split + 1)) - 1 - const baseString = currentId.substring(0, split) - const clonedId = `${baseString}-${clonedFieldNumber}` - - // find cloned field - const clonedField = $(`#${clonedId}`) - if (clonedField.length > 0) { - // copy multiple select - const clonedFieldSelects = $(clonedField).find('select[multiple=true]') - clonedFieldSelects.each(function() { - const currentSelect = $(currentField).find(`select[multiple=true][name=${$(this).prop('name')}]`) - currentSelect.val($(this).val()) - }) - } -} - -const typeUserEvents = {} -for (const key in typeUserAttrs) { - typeUserEvents[key] = { onclone: copyMultipleSelectValues } -} - -function initializeFormbuilder(formAndListIds) { - // FormBuilder conf - formBuilder = $formBuilderContainer.formBuilder({ - showActionButtons: false, - fields, - i18n: { - locale: I18nOption[wiki.locale] ?? 'fr-FR', - location: `${wiki.baseUrl.replace('?', '')}javascripts/vendor/formbuilder-languages/` - }, - templates, - disableFields: [ - 'number', - 'button', - 'autocomplete', - 'checkbox', - 'paragraph', - 'header', - 'collaborative_doc', - 'textarea', - 'checkbox-group', - 'radio-group', - 'select', - 'hidden' - ], - controlOrder: ['text', 'textarea', 'date', 'image', 'url', 'file', 'champs_mail', 'select', 'checkbox-group', 'radio-group', 'map','tags', 'labelhtml', 'titre', 'bookmarklet', 'conditionschecking', 'calc', 'reactions'], - disabledAttrs: [ - 'access', - 'placeholder', - 'className', - 'inline', - 'toggle', - 'description', - 'other', - 'multiple' - ], - typeUserAttrs, - typeUserDisabledAttrs, - typeUserEvents, - inputSets, - onAddField(fieldId, field) { - if (!field.hasOwnProperty('read')) { - field.read = [' * ']// everyone by default - } - if (!field.hasOwnProperty('write')) { - field.write = (field.type === 'champs_mail') - ? [' % '] // owner and @admins by default for e-mail - : [' * '] // everyone by default - } - if (field.type === 'acls' && !field.hasOwnProperty('comment')) { - field.comment = ['comments-closed']// comments-closed by default - } - if (field.type === 'champs_mail' && !('seeEmailAcls' in field)) { - field.seeEmailAcls = [' % ']// owner and @admins by default - } - } - }) - - // disable bf_titre identifier - $('.fld-name').each(function() { - if ($(this).val() === 'bf_titre') { - $(this).attr('disabled', true) - } - }) - - // Each 300ms update the text field converting form bulder content into wiki syntax - let formBuilderInitialized = false - let existingFieldsNames = []; let - existingFieldsIds = [] - - setInterval(() => { - if (!formBuilder || !formBuilder.actions || !formBuilder.actions.setData) return - if (!formBuilderInitialized) { - initializeBuilderFromTextInput() - existingFieldsIds = getFieldsIds() - formBuilderInitialized = true - } - if ($formBuilderTextInput.is(':focus')) return - // Change names - $('.form-group.name-wrap label').text(_t('BAZ_FORM_EDIT_UNIQUE_ID')) - $('.form-group.label-wrap label').text(_t('BAZ_FORM_EDIT_NAME')) - existingFieldsNames = [] - $('.fld-name').each(function() { existingFieldsNames.push($(this).val()) }) - - // Transform input[textarea] in real textarea - $('input[type="textarea"]').replaceWith(function() { - const textarea = document.createElement('textarea') - textarea.id = this.id - textarea.name = this.name - textarea.value = this.value - textarea.classList = this.classList - textarea.title = this.title - textarea.rows = $(this).attr('rows') - return textarea - }) - - // Slugiy field names - $('.fld-name').each(function() { - const newValue = $(this) - .val() - .replace(/[^a-z^A-Z^_^0-9^{^}]/g, '_') - .toLowerCase() - $(this).val(newValue) - }) - - if ($('#form-builder-container').is(':visible')) { - const formData = formBuilder.actions.getData() - const wikiText = formatJsonDataIntoWikiText(formData) - if (wikiText) $formBuilderTextInput.val(wikiText) - } - - // when selecting between data source lists or forms, we need to populate again the listOfFormId select with the - // proper set of options - $('.radio-group-field, .checkbox-group-field, .select-field') - .find('select[name=subtype2]:not(.initialized)') - .change(function() { - $(this).addClass('initialized') - const visibleSelect = $(this) - .closest('.form-field') - .find('select[name=listeOrFormId]') - selectedValue = visibleSelect.val() - visibleSelect.empty() - const optionToAddToSelect = $(this) - .closest('.form-field') - .find(`select[name=${$(this).val()}Id] option`) - visibleSelect.append(new Option('', '', false)) - optionToAddToSelect.each(function() { - const optionKey = $(this).attr('value') - const optionLabel = $(this).text() - const isSelected = optionKey == selectedValue - const newOption = new Option(optionLabel, optionKey, false, isSelected) - visibleSelect.append(newOption) - }) - }) - .trigger('change') - - $('.fld-name').each(function() { - let name = $(this).val() - const id = $(this).closest('.form-field').attr('id') - - // Detect new fields added - if (!existingFieldsIds.includes(id)) { - const fieldType = $(this).closest('.form-field').attr('type') - - // Make the default names easier to read - if (['radio_group', 'checkbox_group', 'select'].includes(fieldType)) { - name = '' - } else if (!name.includes('bf_')) { - name = defaultFieldsName[fieldType] || `bf_${fieldType}` - if (existingFieldsNames.includes(name)) { - // If name already exist, we add a number (bf_address, bf_address1, bf_address2...) - number = 1 - while (existingFieldsNames.includes(name + number)) number += 1 - name += number - } - } - - // if it's a map, we automatically add a bf_addresse - if (fieldType == 'map' && !existingFieldsNames.includes('bf_adresse')) { - const field = { - type: 'text', - subtype: 'text', - name: 'bf_adresse', - label: _t('BAZ_FORM_EDIT_ADDRESS') - } - const index = $(this).closest('.form-field').index() - formBuilder.actions.addField(field, index) - } - } - $(this).val(name) - }) - - existingFieldsIds = getFieldsIds() - - $('.text-field select[name=subtype]:not(.initialized)') - .change(function() { - $(this).addClass('initialized') - $parent = $(this).closest('.form-field') - if ($(this).val() == 'range' || $(this).val() == 'number') { - $parent.find('.maxlength-wrap label').text(_t('BAZ_FORM_EDIT_MAX_VAL')) - $parent.find('.size-wrap label').text(_t('BAZ_FORM_EDIT_MIN_VAL')) - } else { - $parent.find('.maxlength-wrap label').text(_t('BAZ_FORM_EDIT_MAX_LENGTH')) - $parent.find('.size-wrap label').text(_t('BAZ_FORM_EDIT_NB_CHARS')) - } - if ($(this).val() == 'color') { - $parent.find('.maxlength-wrap, .size-wrap').hide() - } else { - $parent.find('.maxlength-wrap, .size-wrap').show() - } - }) - .trigger('change') - - // in semantic field, we want to separate value by coma - $('.fld-semantic').each(function() { - let newVal = $(this) - .val() - .replace(/\s*,\s*/g, ',') - newVal = newVal.replace(/\s+/g, ',') - newVal = newVal.replace(/,+/g, ',') - $(this).val(newVal) - }) - - // Changes icons and icones helpers - $('a[type=remove].icon-cancel') - .removeClass('icon-cancel') - .html('') - $('a[type=copy].icon-copy').attr('title', _t('DUPLICATE')) - $('a[type=edit].icon-pencil').attr('title', _t('BAZ_FORM_EDIT_HIDE')) - }, 300) - - $('#formbuilder-link').click(initializeBuilderFromTextInput) -} - -function getFieldsIds() { - result = [] - $('.fld-name').each(function() { result.push($(this).closest('.form-field').attr('id')) }) - return result -} -// Remove accidental br at the end of the labels -function removeBR(text) { - let newValue = text.replace(/(

<\/div>)+$/g, '') - // replace multiple '

' when at the end of the value - newValue = newValue.replace(/(
)+$/g, '') - // replace multiple '
' when at the end of the value - return newValue -} - -function initializeBuilderFromTextInput() { - const jsonData = parseWikiTextIntoJsonData($formBuilderTextInput.val()) - formBuilder.actions.setData(JSON.stringify(jsonData)) -} - -// transform a json object like "{ type: 'texte', name: 'bf_titre', label: 'Nom' .... }" -// into wiki text like "texte***bf_titre***Nom***255***255*** *** *** ***1***0***" -function formatJsonDataIntoWikiText(formData) { - if (formData.length == 0) return null - let wikiText = '' - - for (let i = 0; i < formData.length; i++) { - const wikiProps = {} - const formElement = formData[i] - const mapping = yesWikiMapping[formElement.type] - - for (const type in yesWikiTypes) { - if ( - formElement.type == yesWikiTypes[type].type - && (!formElement.subtype - || !yesWikiTypes[type].subtype - || formElement.subtype == yesWikiTypes[type].subtype) - && (!formElement.subtype2 - || formElement.subtype2 == yesWikiTypes[type].subtype2) - ) { - wikiProps[0] = type - break - } - } - // for non mapped fields, we just keep the form type - if (!wikiProps[0]) wikiProps[0] = formElement.type - - // fix for url field which can be build with textField or urlField - if (wikiProps[0]) wikiProps[0] = wikiProps[0].replace('_bis', '') - - for (const key in mapping) { - const property = mapping[key] - if (property != 'type') { - let value = formElement[property] - if (['required', 'access'].indexOf(property) > -1) value = value ? '1' : '0' - if (property == 'label') { - wikiProps[key] = removeBR(value).replace(/\n$/gm, '') - } else { - wikiProps[key] = value - } - } - } - - maxProp = Math.max.apply(Math, Object.keys(wikiProps)) - for (let j = 0; j <= maxProp; j++) { - wikiText += wikiProps[j] || ' ' - wikiText += '***' - } - wikiText += '\n' - } - return wikiText -} - -// transform text with wiki text like "texte***bf_titre***Nom***255***255*** *** *** ***1***0***" -// into a json object "{ type: 'texte', name: 'bf_titre', label: 'Nom' .... }" -function parseWikiTextIntoJsonData(text) { - const result = [] - var text = text.trim() - const textFields = text.split('\n') - for (let i = 0; i < textFields.length; i++) { - const textField = textFields[i] - const fieldValues = textField.split('***') - const fieldObject = {} - if (fieldValues.length > 1) { - const wikiType = fieldValues[0] - let fieldType = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].type : wikiType - // check that the fieldType really exists in our form builder - if (!(fieldType in yesWikiMapping)) fieldType = 'custom' - - const mapping = yesWikiMapping[fieldType] - - fieldObject.type = fieldType - fieldObject.subtype = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].subtype : '' - fieldObject.subtype2 = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].subtype2 : '' - const start = fieldType == 'custom' ? 0 : 1 - for (let j = start; j < fieldValues.length; j++) { - let value = fieldValues[j] - const field = mapping && j in mapping ? mapping[j] : j - if (field == 'required') value = value == '1' - if (field) { - if (field == 'read' || field == 'write' || field == 'comment') { - fieldObject[field] = (value.trim() === '') - ? ( - field == 'comment' - ? [' + '] - : [' * '] - ) - : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) - } else if (field == 'seeEmailAcls') { - fieldObject[field] = (value.trim() === '') - ? ' % ' // if not define in tempalte, choose owner and admins - : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) - } else { - fieldObject[field] = value - } - } - } - if (!fieldObject.label) { - fieldObject.label = wikiType - for (let k = 0; k < fields.length; k++) if (fields[k].name == wikiType) fieldObject.label = fields[k].label - } - result.push(fieldObject) - } - } - if (wiki.isDebugEnabled) { - console.log('parse result', result) - } - return result -} - -$('a[href="#formbuilder"]').on('click', (event) => { - if (!confirm(_t('BAZ_FORM_EDIT_CONFIRM_DISPLAY_FORMBUILDER'))) { - event.preventDefault() - return false - } -}) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/README b/tools/bazar/presentation/javascripts/form-edit-template/fields/README new file mode 100644 index 000000000..59673b003 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/README @@ -0,0 +1,5 @@ +export default { + attributes: {}, + // disabledAttributes: [], + // renderInput(fieldData) {}, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/acls.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/acls.js new file mode 100644 index 000000000..b47d6764a --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/acls.js @@ -0,0 +1,108 @@ +import { aclsOptions, aclsCommentOptions } from './commons/attributes.js' +import renderHelper from './commons/render-helper.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_ACL_LABEL'), + name: 'acls', + attrs: { type: 'acls' }, + icon: '' + }, + attributes: { + read: { + label: _t('BAZ_FORM_EDIT_ACL_READ_LABEL'), + options: aclsOptions, + multiple: true + }, + write: { + label: _t('BAZ_FORM_EDIT_ACL_WRITE_LABEL'), + options: aclsOptions, + multiple: true + }, + comment: { + label: _t('BAZ_FORM_EDIT_ACL_COMMENT_LABEL'), + options: aclsCommentOptions, + multiple: true + }, + askIfActivateComments: { + label: _t('BAZ_FORM_EDIT_ACL_ASK_IF_ACTIVATE_COMMENT_LABEL'), + options: { 0: _t('NO'), 1: _t('YES') } + }, + fieldLabel: { + label: _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_LABEL'), + value: '', + placeholder: _t('BAZ_ACTIVATE_COMMENTS') + }, + hint: { + label: _t('BAZ_FORM_EDIT_HELP'), + value: '', + placeholder: _t('BAZ_ACTIVATE_COMMENTS_HINT') + }, + value: { + label: _t('BAZ_FORM_EDIT_COMMENTS_FIELD_DEFAULT_ACTIVATION_LABEL'), + options: { non: _t('NO'), oui: _t('YES'), ' ': '' } + } + }, + disabledAttributes: [ + 'label', 'required' + ], + attributesMapping: { + 0: 'type', + 1: 'read', + 2: 'write', + 3: 'comment', + 4: 'fieldLabel', + 5: 'value', + 6: 'name', + 7: 'askIfActivateComments', + 8: '', + 9: '', + 10: 'hint' + }, + renderInput(field) { + return { + field: field.askIfActivateComments == 1 ? ` ${field.fieldlabel || _t('BAZ_ACTIVATE_COMMENTS')}` : '', + onRender() { + const currentField = renderHelper.getHolder(field).parent() + renderHelper.initializeField(currentField) + $(currentField) + .find('select[name=askIfActivateComments]:not(.initialized)') + .change((event) => { + const element = event.target + + const base = $(element).closest('.acls-field.form-field') + $(element).addClass('initialized') + + const nameInput = $(base).find('input[type=text][name=name]') + if (nameInput.val().trim().length == 0 + || nameInput.val().trim() == 'bf_acls') { + nameInput.val('bf_commentaires') + } + + const visibleSelect = $(base).find('select[name=askIfActivateComments]') + const selectedValue = visibleSelect.val() + + const subElements = $(base) + .find('.form-group.fieldLabel-wrap,.form-group.hint-wrap,.form-group.name-wrap,.form-group.value-wrap') + if ([1, '1'].includes(selectedValue)) { + subElements.show() + const commentInput = $(base).find('select[name=comment]') + const currentValue = commentInput.val() + if (Array.isArray(currentValue) + && ( + currentValue.length == 0 + || (currentValue.length == 1 && currentValue.includes('comments-closed')) + )) { + commentInput.val([' + ']) + } + } else { + subElements.hide() + } + }) + .trigger('change') + renderHelper.defineLabelHintForGroup(field, 'fieldlabel', _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_HINT')) + renderHelper.defineLabelHintForGroup(field, 'hint', _t('BAZ_FORM_EDIT_COMMENTS_FIELD_ACTIVATE_HINT')) + } + } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/bookmarklet.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/bookmarklet.js new file mode 100644 index 000000000..3ead552b0 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/bookmarklet.js @@ -0,0 +1,40 @@ +import renderHelper from './commons/render-helper.js' + +export default { + field: { + label: 'Bookmarklet', + name: 'bookmarklet', + attrs: { type: 'bookmarklet' }, + icon: '' + }, + attributes: { + urlField: { label: _t('BAZ_FORM_EDIT_BOOKMARKLET_URLFIELD_LABEL'), value: 'bf_url' }, + descriptionField: { label: _t('BAZ_FORM_EDIT_BOOKMARKLET_DESCRIPTIONFIELD_LABEL'), value: 'bf_description' }, + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: _t('BAZ_FORM_EDIT_BOOKMARKLET_HINT_DEFAULT_VALUE') }, + text: { label: _t('BAZ_FORM_EDIT_BOOKMARKLET_TEXT_LABEL'), value: _t('BAZ_FORM_EDIT_BOOKMARKLET_TEXT_VALUE') } + }, + disabledAttributes: [ + 'required', 'value' + ], + attributesMapping: { + 0: 'type', + 1: 'name', + 2: 'label', + 3: 'urlField', + 4: 'descriptionField', + 5: 'text', + 6: '', + 7: '', + 8: '', + 9: '', + 10: 'hint' + }, + renderInput(field) { + return { + field: '', + onRender() { + renderHelper.prependHint(field, _t('BAZ_BOOKMARKLET_HINT', { '\\n': '
' })) + } + } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/calc.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/calc.js new file mode 100644 index 000000000..2d00fb27c --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/calc.js @@ -0,0 +1,45 @@ +import { readConf, defaultMapping } from './commons/attributes.js' +import renderHelper from './commons/render-helper.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_CALC_LABEL'), + name: 'calc', + attrs: { type: 'calc' }, + icon: '' + }, + attributes: { + displaytext: { + label: _t('BAZ_FORM_EDIT_DISPLAYTEXT_LABEL'), + value: '', + placeholder: '{value}' + }, + formula: { + label: _t('BAZ_FORM_EDIT_FORMULA_LABEL'), + value: '' + }, + read: readConf + // write: writeconf + }, + disabledAttributes: [ + 'required', 'value', 'default' + ], + attributesMapping: { + ...defaultMapping, + ...{ + 4: 'displaytext', + 5: 'formula', + 8: '', + 9: '' + } + }, + renderInput(field) { + return { + field: '', + onRender() { + renderHelper.prependHint(field, _t('BAZ_FORM_CALC_HINT', { '\\n': '
' })) + renderHelper.defineLabelHintForGroup(field, 'displaytext', _t('BAZ_FORM_EDIT_DISPLAYTEXT_HELP')) + } + } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js new file mode 100644 index 000000000..a36cab589 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js @@ -0,0 +1,115 @@ +import renderHelper from './commons/render-helper.js' +import { readConf, writeconf, semanticConf, defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_EMAIL_LABEL'), + name: 'champs_mail', + attrs: { type: 'champs_mail' }, + icon: '' + }, + attributes: { + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, + separator: { label: '' }, // separate important attrs from others + send_form_content_to_this_email: { + label: _t('BAZ_FORM_EDIT_EMAIL_SEND_FORM_CONTENT_LABEL'), + options: { 0: _t('NO'), 1: _t('YES') } + }, + replace_email_by_button: { + label: _t('BAZ_FORM_EDIT_EMAIL_REPLACE_BY_BUTTON_LABEL'), + options: { '': _t('NO'), form: _t('YES') }, + value: 'form' + }, + seeEmailAcls: { ...readConf, ...{ label: _t('BAZ_FORM_EDIT_EMAIL_SEE_MAIL_ACLS') } }, + readWhenForm: { ...readConf, ...{ label: _t('BAZ_FORM_EDIT_EMAIL_SEND_ACLS') } }, + // searchable: searchableConf, -> 10/19 Florian say that this conf is not working for now + read: readConf, + write: writeconf, + semantic: semanticConf + }, + // disabledAttributes: [], + attributesMapping: { + ...defaultMapping, + ...{ 4: 'seeEmailAcls', 6: 'replace_email_by_button', 9: 'send_form_content_to_this_email' } + }, + renderInput(fieldData) { + return { + field: ``, + onRender() { + const currentField = renderHelper.getHolder(fieldData).parent() + renderHelper.initializeField(currentField) + const arrayEquals = (a, b) => { + if (a.length != b.length) { + return false + } + return (a.every((e) => b.includes(e)) && b.every((e) => a.includes(e))) + } + currentField.find('select[name=read]:not(.initialized)') + .on('change', (event) => { + const element = event.target + const base = $(element).closest('.champs_mail-field.form-field') + $(element).addClass('initialized') + + const readWhenFormInput = $(base).find('select[name=readWhenForm]') + if (readWhenFormInput && readWhenFormInput.length > 0 && !arrayEquals(readWhenFormInput.val(), $(element).val())) { + readWhenFormInput.val($(element).val()) + } + }).trigger('change') + currentField.find('select[name=readWhenForm]:not(.initialized)') + .on('change', (event) => { + const element = event.target + const base = $(element).closest('.champs_mail-field.form-field') + $(element).addClass('initialized') + + const readInput = $(base).find('select[name=read]') + if (readInput && readInput.length > 0 && !arrayEquals(readInput.val(), $(element).val())) { + readInput.val($(element).val()) + } + }).trigger('change') + currentField + .find('select[name=replace_email_by_button]:not(.initialized)') + .on('change', (event) => { + const element = event.target + + const base = $(element).closest('.champs_mail-field.form-field') + $(element).addClass('initialized') + + const setDisplay = (base, name, newValue) => { + const wrapper = $(base).find(`div.form-group.${name}-wrap`) + if (wrapper && wrapper.length > 0) { + if (newValue) { + wrapper.show() + } else { + wrapper.hide() + } + } + } + if ($(element).val() == 'form') { + // when chosing 'form' (or at init), if readAcl is ' % ', prefer ' * ' + // to show button to everyone + const field = currentField.find('select[name=read]') + if (arrayEquals(field.val(), [' % '])) { + field.val([' * ']) + field.trigger('change') + } + setDisplay(base, 'readWhenForm', 1) + setDisplay(base, 'seeEmailAcls', 1) + setDisplay(base, 'read', 0) + } else { + // when chosing 'text' (or at init), if readAcl is ' * ', prefer ' % ' + // to force email not to be shown + const field = currentField.find('select[name=read]') + if (arrayEquals(field.val(), [' * ']) && !currentField.find('select[name=write]').val().includes(' * ')) { + field.val([' % ']) + field.trigger('change') + } + setDisplay(base, 'readWhenForm', 0) + setDisplay(base, 'seeEmailAcls', 0) + setDisplay(base, 'read', 1) + } + }) + .trigger('change') + } + } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/checkbox-group.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/checkbox-group.js new file mode 100644 index 000000000..5e4cf3a09 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/checkbox-group.js @@ -0,0 +1,31 @@ +import { selectConf, listsMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_CHECKBOX_LABEL'), + name: 'checkbox-group', + attrs: { type: 'checkbox-group' }, + icon: '' + }, + attributes: { + ...selectConf, + ...{ + fillingMode: { + label: _t('BAZ_FORM_EDIT_FILLING_MODE_LABEL'), + options: { + ' ': _t('BAZ_FORM_EDIT_FILLING_MODE_NORMAL'), + tags: _t('BAZ_FORM_EDIT_FILLING_MODE_TAGS'), + dragndrop: _t('BAZ_FORM_EDIT_FILLING_MODE_DRAG_AND_DROP') + } + }, + queries: { + label: _t('BAZ_FORM_EDIT_QUERIES_LABEL'), + value: '', + placeholder: 'ex. : checkboxfiche6=PageTag ; cf. https://yeswiki.net/?LierFormulairesEntreEux' + } + } + }, + // disabledAttributes: [], + attributesMapping: { ...listsMapping, ...{ 7: 'fillingMode' } }, + // renderInput(fieldData) {}, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/collaborative_doc.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/collaborative_doc.js new file mode 100644 index 000000000..a46705ed9 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/collaborative_doc.js @@ -0,0 +1,11 @@ +export default { + field: { + name: 'collaborative_doc', + attrs: { type: 'collaborative_doc' } + }, + attributes: {}, + // disabledAttributes: [], + renderInput(field) { + return { field: _t('BAZ_FORM_EDIT_COLLABORATIVE_DOC_FIELD') } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/attributes.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/attributes.js new file mode 100644 index 000000000..e7ef3c815 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/attributes.js @@ -0,0 +1,131 @@ +// groupsList and formAndListIds variables are created in forms_form.twig + +const $formBuilderTextInput = $('#form-builder-text') + +// When user add manuall via wikiCode a list or a formId that does not exist, keep the value +// so it can be added in the select option list +const listAndFormUserValues = {} +$formBuilderTextInput.val().trim().split('\n').forEach((textField) => { + const fieldValues = textField.split('***') + if (fieldValues.length > 1) { + const [field, value] = fieldValues + if (['checkboxfiche', 'checkbox', 'liste', 'radio', 'listefiche', 'radiofiche'].includes(field) + && value && !(value in formAndListIds)) { + listAndFormUserValues[value] = value + } + } +}) + +// Some attributes configuration used in multiple fields +export const visibilityOptions = { + ' * ': _t('EVERYONE'), + ' + ': _t('IDENTIFIED_USERS'), + ' % ': _t('BAZ_FORM_EDIT_OWNER_AND_ADMINS'), + '@admins': _t('MEMBER_OF_GROUP', { groupName: 'admin' }) +} + +// create list of user groups +// groupsList variable is defined in forms_form.twig +const _formattedGroupList = [] +groupsList.map((group) => { + _formattedGroupList[`@${group}`] = _t('MEMBER_OF_GROUP', { groupName: group }) +}) +export const formattedGroupList = _formattedGroupList + +export const aclsOptions = { + ...visibilityOptions, + ...{ + user: + _t('BAZ_FORM_EDIT_USER') + }, + ...formattedGroupList +} + +export const aclsCommentOptions = { + ...{ 'comments-closed': _t('BAZ_FORM_EDIT_COMMENTS_CLOSED') }, + ...visibilityOptions, + ...{ user: _t('BAZ_FORM_EDIT_USER') }, + ...formattedGroupList +} + +export const readConf = { + label: _t('BAZ_FORM_EDIT_CAN_BE_READ_BY'), + options: { ...visibilityOptions, ...formattedGroupList }, + multiple: true +} + +export const writeconf = { + label: _t('BAZ_FORM_EDIT_CAN_BE_WRITTEN_BY'), + options: { ...visibilityOptions, ...formattedGroupList }, + multiple: true +} + +export const searchableConf = { + label: _t('BAZ_FORM_EDIT_SEARCH_LABEL'), + options: { '': _t('NO'), 1: _t('YES') } +} + +export const semanticConf = { + label: _t('BAZ_FORM_EDIT_SEMANTIC_LABEL'), + value: '', + placeholder: 'Ex: https://schema.org/name' +} + +export const selectConf = { + subtype2: { + label: _t('BAZ_FORM_EDIT_SELECT_SUBTYPE2_LABEL'), + options: { + list: _t('BAZ_FORM_EDIT_SELECT_SUBTYPE2_LIST'), + form: _t('BAZ_FORM_EDIT_SELECT_SUBTYPE2_FORM') + } + }, + listeOrFormId: { + label: _t('BAZ_FORM_EDIT_SELECT_LIST_FORM_ID'), + options: { + ...{ '': '' }, + ...formAndListIds.lists, + ...formAndListIds.forms, + ...listAndFormUserValues + } + }, + listId: { + label: '', + options: { ...formAndListIds.lists, ...listAndFormUserValues } + }, + formId: { + label: '', + options: { ...formAndListIds.forms, ...listAndFormUserValues } + }, + defaultValue: { + label: _t('BAZ_FORM_EDIT_SELECT_DEFAULT'), + value: '' + }, + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, + read: readConf, + write: writeconf, + semantic: semanticConf +} + +// Mapping betwwen yes wiki syntax and FormBuilder json syntax +export const defaultMapping = { + 0: 'type', + 1: 'name', + 2: 'label', + 3: 'size', + 4: 'maxlength', + 5: 'value', + 6: 'pattern', + 7: 'subtype', + 8: 'required', + 9: 'searchable', + 10: 'hint', + 11: 'read', + 12: 'write', + 14: 'semantic', + 15: 'queries' +} + +export const listsMapping = { + ...defaultMapping, + ...{ 1: 'listeOrFormId', 5: 'defaultValue', 6: 'name' } +} \ No newline at end of file diff --git a/tools/bazar/presentation/javascripts/form-edit-template-helper.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/render-helper.js similarity index 99% rename from tools/bazar/presentation/javascripts/form-edit-template-helper.js rename to tools/bazar/presentation/javascripts/form-edit-template/fields/commons/render-helper.js index 674ddb491..46bf76adf 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template-helper.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/render-helper.js @@ -1,5 +1,5 @@ // function to render help for tabs and tabchange -const templateHelper = { +export default { cache: {}, holders: {}, ids: {}, diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/conditionschecking.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/conditionschecking.js new file mode 100644 index 000000000..4291750eb --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/conditionschecking.js @@ -0,0 +1,46 @@ +import renderHelper from './commons/render-helper.js' +import { defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL'), + name: 'conditionschecking', + attrs: { type: 'conditionschecking' }, + icon: '' + }, + attributes: { + condition: { + label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL'), + value: '' + }, + clean: { + label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_CLEAN_LABEL'), + options: { + ' ': _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_CLEAN_OPTION'), + noclean: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_NOCLEAN_OPTION') + } + } + }, + disabledAttributes: [ + 'required', 'value', 'name', 'label' + ], + attributesMapping: { + ...defaultMapping, + ...{ + 1: 'condition', + 2: 'clean', + 5: '', + 8: '', + 9: '' + } + }, + renderInput(data) { + return { + field: '', + onRender() { + renderHelper.prependHint(data, _t('BAZ_FORM_CONDITIONSCHEKING_HINT', { '\\n': '
' })) + renderHelper.defineLabelHintForGroup(data, 'noclean', _t('BAZ_FORM_CONDITIONSCHEKING_NOCLEAN_HINT')) + } + } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/custom.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/custom.js new file mode 100644 index 000000000..48a746e3a --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/custom.js @@ -0,0 +1,48 @@ +export default { + field: { + label: _t('BAZ_FORM_EDIT_CUSTOM_LABEL'), + name: 'custom', + attrs: { type: 'custom' }, + icon: '' + }, + attributes: { + param0: { label: 'Param0', value: '' }, + param1: { label: 'Param1', value: '' }, + param2: { label: 'Param2', value: '' }, + param3: { label: 'Param3', value: '' }, + param4: { label: 'Param4', value: '' }, + param5: { label: 'Param5', value: '' }, + param6: { label: 'Param6', value: '' }, + param7: { label: 'Param7', value: '' }, + param8: { label: 'Param8', value: '' }, + param9: { label: 'Param9', value: '' }, + param10: { label: 'Param10', value: '' }, + param11: { label: 'Param11', value: '' }, + param12: { label: 'Param12', value: '' }, + param13: { label: 'Param13', value: '' }, + param14: { label: 'Param14', value: '' }, + param15: { label: 'Param15', value: '' } + }, + // disabledAttributes: [], + attributesMapping: { + 0: 'param0', + 1: 'param1', + 2: 'param2', + 3: 'param3', + 4: 'param4', + 5: 'param5', + 6: 'param6', + 7: 'param7', + 8: 'param8', + 9: 'param9', + 10: 'param10', + 11: 'param11', + 12: 'param12', + 13: 'param13', + 14: 'param14', + 15: 'param15' + }, + renderInput(field) { + return { field: '' } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js new file mode 100644 index 000000000..3990277f3 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js @@ -0,0 +1,23 @@ +import { readConf, writeconf, semanticConf, defaultMapping } from './commons/attributes.js' + +export default { + // field: { + // label: "Sélecteur de date", + // name: "jour", + // attrs: { type: "date" }, + // icon: '', + // }, + attributes: { + today_button: { + label: _t('BAZ_FORM_EDIT_DATE_TODAY_BUTTON'), + options: { ' ': _t('NO'), today: _t('YES') } + }, + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, + read: readConf, + write: writeconf, + semantic: semanticConf + }, + // disabledAttributes: [], + attributesMapping: { ...defaultMapping, ...{ 5: 'today_button' } }, + // renderInput(fieldData) {}, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/file.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/file.js new file mode 100644 index 000000000..c8d5a434a --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/file.js @@ -0,0 +1,19 @@ +import { readConf, writeconf, semanticConf, defaultMapping } from './commons/attributes.js' + +export default { + attributes: { + readlabel: { + label: _t('BAZ_FORM_EDIT_FILE_READLABEL_LABEL'), + value: '', + placeholder: _t('BAZ_FILEFIELD_FILE') + }, + maxsize: { label: _t('BAZ_FORM_EDIT_FILE_MAXSIZE_LABEL'), value: '' }, + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, + read: readConf, + write: writeconf, + semantic: semanticConf + }, + // disabledAttributes: [], + attributesMapping: { ...defaultMapping, ...{ 3: 'maxsize', 6: 'readlabel' } }, + // renderInput(fieldData) {}, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/hidden.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/hidden.js new file mode 100644 index 000000000..cd5a92829 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/hidden.js @@ -0,0 +1,6 @@ +export default { + attributes: {}, + // disabledAttributes: [], + attributesMapping: { 0: 'type', 1: 'name', 5: 'value' }, + // renderInput(fieldData) {}, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js new file mode 100644 index 000000000..46a093a7c --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js @@ -0,0 +1,40 @@ +import { readConf, writeconf, semanticConf, defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_IMAGE_LABEL'), + name: 'image', + attrs: { type: 'image' }, + icon: '' + }, + attributes: { + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, + thumb_height: { label: _t('BAZ_FORM_EDIT_IMAGE_HEIGHT'), value: '300' }, + thumb_width: { label: _t('BAZ_FORM_EDIT_IMAGE_WIDTH'), value: '400' }, + resize_height: { label: _t('BAZ_FORM_EDIT_IMAGE_HEIGHT_RESIZE'), value: '600' }, + resize_width: { label: _t('BAZ_FORM_EDIT_IMAGE_WIDTH_RESIZE'), value: '800' }, + align: { + label: _t('BAZ_FORM_EDIT_IMAGE_ALIGN_LABEL'), + value: 'right', + options: { left: _t('LEFT'), right: _t('RIGHT') } + }, + read: readConf, + write: writeconf, + semantic: semanticConf + }, + // disabledAttributes: [], + attributesMapping: { + ...defaultMapping, + ...{ + 1: 'name', + 3: 'thumb_height', + 4: 'thumb_width', + 5: 'resize_height', + 6: 'resize_width', + 7: 'align' + } + }, + renderInput(fieldData) { + return { field: '' } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/inscriptionliste.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/inscriptionliste.js new file mode 100644 index 000000000..90a2dd69c --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/inscriptionliste.js @@ -0,0 +1,30 @@ +export default { + field: { + label: _t('BAZ_FORM_EDIT_SUBSCRIBE_LIST_LABEL'), + name: 'inscriptionliste', + attrs: { type: 'inscriptionliste' }, + icon: '' + }, + attributes: { + subscription_email: { label: _t('BAZ_FORM_EDIT_INSCRIPTIONLISTE_EMAIL_LABEL'), value: '' }, + email_field_id: { + label: _t('BAZ_FORM_EDIT_INSCRIPTIONLISTE_EMAIL_FIELDID'), + value: 'bf_mail' + }, + mailing_list_tool: { + label: _t('BAZ_FORM_EDIT_INSCRIPTIONLISTE_MAILINGLIST'), + value: '' + } + }, + // disabledAttributes: [], + attributesMapping: { + 0: 'type', + 1: 'subscription_email', + 2: 'label', + 3: 'email_field_id', + 4: 'mailing_list_tool' + }, + renderInput(field) { + return { field: '' } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/labelhtml.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/labelhtml.js new file mode 100644 index 000000000..528e9d850 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/labelhtml.js @@ -0,0 +1,40 @@ +export default { + field: { + label: _t('BAZ_FORM_EDIT_CUSTOM_HTML_LABEL'), + name: 'labelhtml', + attrs: { type: 'labelhtml' }, + icon: '' + }, + attributes: { + label: { + label: _t('BAZ_FORM_EDIT_CUSTOM_HTML_LABEL'), + value: '' + }, + content_saisie: { + label: _t('BAZ_FORM_EDIT_EDIT_CONTENT_LABEL'), + type: 'textarea', + rows: '4', + value: '' + }, + content_display: { + label: _t('BAZ_FORM_EDIT_VIEW_CONTENT_LABEL'), + type: 'textarea', + rows: '4', + value: '' + } + }, + // disabledAttributes: [], + attributesMapping: { + 0: 'type', + 1: 'content_saisie', + 2: '', + 3: 'content_display' + }, + renderInput(field) { + return { + field: + `
${field.content_saisie || ''}
+
${field.content_display || ''}
` + } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js new file mode 100644 index 000000000..a090fe83e --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js @@ -0,0 +1,52 @@ +import { readConf, writeconf, semanticConf } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_LINKEDENTRIES_LABEL'), + name: 'listefichesliees', + attrs: { type: 'listefichesliees' }, + icon: '' + }, + attributes: { + id: { label: _t('BAZ_FORM_EDIT_LISTEFICHES_FORMID_LABEL'), value: '' }, + query: { + label: _t('BAZ_FORM_EDIT_LISTEFICHES_QUERY_LABEL'), + value: '', + placeholder: _t('BAZ_FORM_EDIT_LISTEFICHES_QUERY_PLACEHOLDER', { url: 'https://yeswiki.net/?DocQuery/iframe' }) + }, + param: { + label: _t('BAZ_FORM_EDIT_LISTEFICHES_PARAMS_LABEL'), + value: '', + placeholder: 'Ex: champs="bf_nom" ordre="desc"' + }, + number: { label: _t('BAZ_FORM_EDIT_LISTEFICHES_NUMBER_LABEL'), value: '', placeholder: '' }, + template: { + label: _t('BAZ_FORM_EDIT_LISTEFICHES_TEMPLATE_LABEL'), + value: '', + placeholder: + _t('BAZ_FORM_EDIT_LISTEFICHES_TEMPLATE_PLACEHOLDER') + }, + type_link: { + label: _t('BAZ_FORM_EDIT_LISTEFICHES_LISTTYPE_LABEL'), + value: '', + placeholder: + _t('BAZ_FORM_EDIT_LISTEFICHES_LISTTYPE_PLACEHOLDER') + }, + read: readConf, + write: writeconf, + semantic: semanticConf + }, + // disabledAttributes: [], + attributesMapping: { + 0: 'type', + 1: 'id', + 2: 'query', + 3: 'param', + 4: 'number', + 5: 'template', + 6: 'type_link' + }, + renderInput(field) { + return { field: '' } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js new file mode 100644 index 000000000..bf3988445 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js @@ -0,0 +1,195 @@ +import renderHelper from './commons/render-helper.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_GEO_LABEL'), + name: 'map', + attrs: { type: 'map' }, + icon: '' + }, + attributes: { + name_latitude: { label: _t('BAZ_FORM_EDIT_MAP_LATITUDE'), value: 'bf_latitude' }, + name_longitude: { label: _t('BAZ_FORM_EDIT_MAP_LONGITUDE'), value: 'bf_longitude' }, + autocomplete_street: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET_PLACEHOLDER') }, + autocomplete_postalcode: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_POSTALCODE'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_POSTALCODE_PLACEHOLDER') }, + autocomplete_town: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_TOWN'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_TOWN_PLACEHOLDER') }, + autocomplete_county: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_COUNTY'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_COUNTY_PLACEHOLDER') }, + autocomplete_state: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STATE'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STATE_PLACEHOLDER') }, + autocomplete_other: { label: '', value: '' }, + autocomplete_street1: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET1'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET1_PLACEHOLDER') }, + autocomplete_street2: { label: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET2'), value: '', placeholder: _t('BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_STREET2_PLACEHOLDER') }, + geolocate: { + label: _t('BAZ_FORM_EDIT_GEOLOCATE'), + options: { 0: _t('NO'), 1: _t('YES') } + } + }, + // disabledAttributes: [], + attributesMapping: { + 0: 'type', + 1: 'name_latitude', + 2: 'name_longitude', + 3: '', + 4: 'autocomplete_postalcode', + 5: 'autocomplete_town', + 6: 'autocomplete_other', + 8: 'required' + }, + renderInput(fieldData) { + return { + field: _t('BAZ_FORM_EDIT_MAP_FIELD'), + onRender() { + const toggleState = function(name, state) { + const formGroup = renderHelper.getFormGroup(fieldData, name) + if (formGroup !== null) { + if (state === 'show') { + formGroup.show() + } else { + formGroup.hide() + } + } + } + const toggleStates = function(state) { + ['autocomplete_street1', 'autocomplete_street2'].forEach((name) => toggleState(name, state)) + } + // initMapAutocompleteUpdate() + $('.map-field.form-field') + .find('input[type=text][name=autocomplete_street]:not(.initialized)' + + ',input[type=text][name=autocomplete_street1]:not(.initialized)' + + ',input[type=text][name=autocomplete_street2]:not(.initialized)' + + ',input[type=text][name=autocomplete_county]:not(.initialized)' + + ',input[type=text][name=autocomplete_state]:not(.initialized)' + + ',select[name=geolocate]:not(.initialized)') + .on('change', (event) => { + // mapAutocompleteUpdate(event.target) + const element = event.target + const base = $(element).closest('.map-field.form-field') + if (!$(element).hasClass('initialized')) { + $(element).addClass('initialized') + if ($(element).val().length == 0 || $(element).prop('tagName') === 'SELECT') { + // mapAutocompleteUpdateExtractFromOther(base) + const other = { + geolocate: '', + street: '', + street1: '', + street2: '', + county: '', + state: '' + } + const autoCompleteOther = $(base) + .find('input[type=text][name=autocomplete_other]') + .first() + if (autoCompleteOther && autoCompleteOther.length > 0) { + const value = autoCompleteOther.val().split('|') + other.geolocate = ['1', 1, true].includes(value[0]) ? '1' : '0' + other.street = value[1] || '' + other.street1 = value[2] || '' + other.street2 = value[3] || '' + other.county = value[4] || '' + other.state = value[5] || '' + } + switch (element.getAttribute('name')) { + case 'autocomplete_street': + $(element).val(other.street) + break + case 'autocomplete_street1': + $(element).val(other.street1) + break + case 'autocomplete_street2': + $(element).val(other.street2) + break + case 'autocomplete_county': + $(element).val(other.county) + break + case 'autocomplete_state': + $(element).val(other.state) + break + case 'geolocate': + $(element).val(other.geolocate === '1' ? '1' : '0') + break + default: + break + } + } + } else { + // autocompleteUpdateSaveToOther(base) + const autoCompleteOther = $(base) + .find('input[type=text][name=autocomplete_other]') + .first() + if (autoCompleteOther && autoCompleteOther.length > 0) { + const results = { + geolocate: '', + street: '', + street1: '', + street2: '', + county: '', + state: '' + } + const associations = { + street: 'autocomplete_street', + street1: 'autocomplete_street1', + street2: 'autocomplete_street2', + county: 'autocomplete_county', + state: 'autocomplete_state' + } + for (const key in associations) { + const autoCompleteField = $(base) + .find(`input[type=text][name=${associations[key]}]`) + .first() + if (autoCompleteField && autoCompleteField.length > 0) { + results[key] = autoCompleteField.val() || '' + } + } + // geolocate + const geolocateField = $(base) + .find('select[name=geolocate]') + .first() + if (geolocateField && geolocateField.length > 0) { + results.geolocate = geolocateField.val() || '' + } + autoCompleteOther.val( + `${results.geolocate + }|${results.street}` + + `|${results.street1}` + + `|${results.street2}` + + `|${results.county}` + + `|${results.state}` + ) + } + } + }) + .trigger('change') + + renderHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street', ` +
+ ${_t('GEOLOCATER_GROUP_GEOLOCATIZATION')} +
${_t('GEOLOCATER_GROUP_GEOLOCATIZATION_HINT')}
+
+ `) + const $advancedParams = $(` +
+ +
+ +
+
+ `) + renderHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street1', $advancedParams) + $advancedParams.find('button').on('click', function(event) { + if ($(this).hasClass('opened')) { + $(this).removeClass('opened') + $(this).html(_t('GEOLOCATER_SEE_ADVANCED_PARAMS')) + toggleStates('hide') + } else { + $(this).addClass('opened') + $(this).html(_t('GEOLOCATER_HIDE_ADVANCED_PARAMS')) + toggleStates('show') + } + event.preventDefault() + event.stopPropagation() + }) + toggleStates('hide') + renderHelper.prependHTMLBeforeGroup(fieldData, 'geolocate', '

') + } + } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/metadatas.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/metadatas.js new file mode 100644 index 000000000..e085c1385 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/metadatas.js @@ -0,0 +1,36 @@ +export default { + field: { + label: _t('BAZ_FORM_EDIT_METADATA_LABEL'), + name: 'metadatas', + attrs: { type: 'metadatas' }, + icon: '' + }, + attributes: { + theme: { + label: _t('BAZ_FORM_EDIT_METADATA_THEME_LABEL'), + value: '', + placeholder: 'margot, interface, colibris' + }, + squelette: { label: _t('BAZ_FORM_EDIT_METADATA_SQUELETON_LABEL'), value: '1col.tpl.html' }, + style: { + label: _t('BAZ_FORM_EDIT_METADATA_STYLE_LABEL'), + value: '', + placeholder: 'bootstrap.css...' + }, + preset: { + label: _t('BAZ_FORM_EDIT_METADATA_PRESET_LABEL'), + value: '', + placeholder: `blue.css (${_t('BAZ_FORM_EDIT_METADATA_PRESET_PLACEHOLDER')})` + }, + image: { + label: _t('BAZ_FORM_EDIT_METADATA_BACKGROUND_IMAGE_LABEL'), + value: '', + placeholder: 'foret.jpg...' + } + }, + // disabledAttributes: [], + attributesMapping: { 0: 'type', 1: 'theme', 2: 'squelette', 3: 'style', 4: 'image', 5: 'preset' }, + renderInput(field) { + return { field: '' } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/radio-group.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/radio-group.js new file mode 100644 index 000000000..f7631609c --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/radio-group.js @@ -0,0 +1,30 @@ +import { selectConf, listsMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_RADIO_LABEL'), + name: 'radio-group', + attrs: { type: 'radio-group' }, + icon: '' + }, + attributes: { + ...selectConf, + ...{ + fillingMode: { + label: _t('BAZ_FORM_EDIT_FILLING_MODE_LABEL'), + options: { + ' ': _t('BAZ_FORM_EDIT_FILLING_MODE_NORMAL'), + tags: _t('BAZ_FORM_EDIT_FILLING_MODE_TAGS') + } + }, + queries: { + label: _t('BAZ_FORM_EDIT_QUERIES_LABEL'), + value: '', + placeholder: 'ex. : checkboxfiche6=PageTag ; cf. https://yeswiki.net/?LierFormulairesEntreEux' + } + } + }, + // disabledAttributes: [], + attributesMapping: { ...listsMapping, ...{ 7: 'fillingMode' } }, + // renderInput(fieldData) {}, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/reactions.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/reactions.js new file mode 100644 index 000000000..638d99a5d --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/reactions.js @@ -0,0 +1,61 @@ +import renderHelper from './commons/render-helper.js' +import { readConf, writeconf, semanticConf, defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_REACTIONS_FIELD'), + name: 'reactions', + attrs: { type: 'reactions' }, + icon: '' + }, + attributes: { + fieldlabel: { + label: _t('BAZ_REACTIONS_FIELD_ACTIVATE_LABEL'), + value: '', + placeholder: _t('BAZ_ACTIVATE_REACTIONS') + }, + value: { + label: _t('BAZ_REACTIONS_FIELD_DEFAULT_ACTIVATION_LABEL'), + options: { oui: _t('YES'), non: _t('NO') } + }, + labels: { + label: _t('BAZ_REACTIONS_FIELD_LABELS_LABEL'), + value: '' + }, + images: { + label: _t('BAZ_REACTIONS_FIELD_IMAGES_LABEL'), + value: '', + placeholder: _t('BAZ_REACTIONS_FIELD_IMAGES_PLACEHOLDER') + }, + ids: { + label: _t('BAZ_REACTIONS_FIELD_IDS_LABEL'), + value: '' + }, + read: readConf, + write: writeconf, + semantic: semanticConf + }, + disabledAttributes: [ + 'label', 'required' + ], + attributesMapping: { + ...defaultMapping, + ...{ + 2: 'ids', + 3: 'labels', + 4: 'images', + 6: 'fieldlabel' + } + }, + renderInput(field) { + return { + field: ` ${field.fieldlabel || _t('BAZ_ACTIVATE_REACTIONS')}`, + onRender() { + renderHelper.defineLabelHintForGroup(field, 'fieldlabel', _t('BAZ_REACTIONS_FIELD_ACTIVATE_HINT')) + renderHelper.defineLabelHintForGroup(field, 'ids', _t('BAZ_REACTIONS_FIELD_IDS_HINT')) + renderHelper.defineLabelHintForGroup(field, 'images', _t('BAZ_REACTIONS_FIELD_IMAGES_HINT')) + renderHelper.defineLabelHintForGroup(field, 'labels', _t('BAZ_REACTIONS_FIELD_LABELS_HINT')) + } + } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/select.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/select.js new file mode 100644 index 000000000..4e52c54fc --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/select.js @@ -0,0 +1,23 @@ +import { selectConf, listsMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_SELECT_LABEL'), + name: 'select', + attrs: { type: 'select' }, + icon: '' + }, + attributes: { + ...selectConf, + ...{ + queries: { + label: _t('BAZ_FORM_EDIT_QUERIES_LABEL'), + value: '', + placeholder: 'ex. : checkboxfiche6=PageTag ; cf. https://yeswiki.net/?LierFormulairesEntreEux' + } + } + }, + // disabledAttributes: [], + attributesMapping: listsMapping, + // renderInput(fieldData) {}, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/tabchange.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/tabchange.js new file mode 100644 index 000000000..7f7b265d4 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/tabchange.js @@ -0,0 +1,47 @@ +import renderHelper from './commons/render-helper.js' +import { defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_TABCHANGE'), + name: 'tabchange', + attrs: { type: 'tabchange' }, + icon: '' + }, + attributes: { + formChange: { + label: _t('BAZ_FORM_EDIT_TABS_FOR_FORM'), + options: { formChange: _t('YES'), noformchange: _t('NO') }, + description: `${_t('BAZ_FORM_EDIT_TABCHANGE_CHANGE_LABEL')} ${_t('BAZ_FORM_EDIT_TABS_FOR_FORM')}` + }, + viewChange: { + label: _t('BAZ_FORM_EDIT_TABS_FOR_ENTRY'), + options: { '': _t('NO'), viewChange: _t('YES') }, + description: `${_t('BAZ_FORM_EDIT_TABCHANGE_CHANGE_LABEL')} ${_t('BAZ_FORM_EDIT_TABS_FOR_ENTRY')}` + } + }, + disabledAttributes: [ + 'required', 'value', 'name', 'label' + ], + attributesMapping: { + ...defaultMapping, + ...{ + 1: 'formChange', + 2: '', + 3: 'viewChange' + } + }, + renderInput(field) { + return { + field: '', + onRender() { + renderHelper.prependHint(field, _t('BAZ_FORM_TABS_HINT', { + '\\n': '
', + 'tabs-field-label': _t('BAZ_FORM_EDIT_TABS'), + 'tabchange-field-label': _t('BAZ_FORM_EDIT_TABCHANGE') + })) + renderHelper.prependHTMLBeforeGroup(field, 'formChange', $('
').addClass('form-group').append($('').append(_t('BAZ_FORM_EDIT_TABCHANGE_CHANGE_LABEL')))) + } + } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/tabs.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/tabs.js new file mode 100644 index 000000000..3ce3d8c78 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/tabs.js @@ -0,0 +1,80 @@ +import renderHelper from './commons/render-helper.js' +import { defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_TABS'), + name: 'tabs', + attrs: { type: 'tabs' }, + icon: '' + }, + attributes: { + formTitles: { + label: _t('BAZ_FORM_EDIT_TABS_FOR_FORM'), + value: _t('BAZ_FORM_EDIT_TABS_FORMTITLES_VALUE'), + placeholder: _t('BAZ_FORM_EDIT_TABS_FORMTITLES_DESCRIPTION'), + description: _t('BAZ_FORM_EDIT_TABS_FORMTITLES_DESCRIPTION') + }, + viewTitles: { + label: _t('BAZ_FORM_EDIT_TABS_FOR_ENTRY'), + value: '', + placeholder: _t('BAZ_FORM_EDIT_TABS_VIEWTITLES_DESCRIPTION'), + description: _t('BAZ_FORM_EDIT_TABS_VIEWTITLES_DESCRIPTION') + }, + moveSubmitButtonToLastTab: { + label: _t('BAZ_FORM_EDIT_TABS_MOVESUBMITBUTTONTOLASTTAB_LABEL'), + options: { '': _t('NO'), moveSubmit: _t('YES') }, + description: _t('BAZ_FORM_EDIT_TABS_MOVESUBMITBUTTONTOLASTTAB_DESCRIPTION') + }, + btnColor: { + label: _t('BAZ_FORM_EDIT_TABS_BTNCOLOR_LABEL'), + options: { 'btn-primary': _t('PRIMARY'), 'btn-secondary-1': `${_t('SECONDARY')} 1`, 'btn-secondary-2': `${_t('SECONDARY')} 2` } + }, + btnSize: { + label: _t('BAZ_FORM_EDIT_TABS_BTNSIZE_LABEL'), + options: { '': _t('NORMAL_F'), 'btn-xs': _t('SMALL_F') } + } + }, + disabledAttributes: [ + 'required', 'value', 'name', 'label' + ], + attributesMapping: { + ...defaultMapping, + ...{ + 1: 'formTitles', + 2: '', + 3: 'viewTitles', + 5: 'moveSubmitButtonToLastTab', + 6: '', + 7: 'btnColor', + 9: 'btnSize' + } + }, + renderInput(field) { + return { + field: '', + onRender() { + renderHelper.prependHint(field, _t('BAZ_FORM_TABS_HINT', { + '\\n': '
', + 'tabs-field-label': _t('BAZ_FORM_EDIT_TABS'), + 'tabchange-field-label': _t('BAZ_FORM_EDIT_TABCHANGE') + })) + renderHelper.prependHTMLBeforeGroup(field, 'formTitles', $('
').addClass('form-group').append($('').append(_t('BAZ_FORM_EDIT_TABS_TITLES_LABEL')))) + renderHelper.defineLabelHintForGroup(field, 'formTitles', _t('BAZ_FORM_EDIT_TABS_FORMTITLES_DESCRIPTION')) + renderHelper.defineLabelHintForGroup(field, 'viewTitles', _t('BAZ_FORM_EDIT_TABS_VIEWTITLES_DESCRIPTION')) + renderHelper.prependHTMLBeforeGroup(field, 'moveSubmitButtonToLastTab', $('
').addClass('form-group')) + + const holder = renderHelper.getHolder(field) + if (holder) { + const formGroup = holder.find('.formTitles-wrap') + if (typeof formGroup !== undefined && formGroup.length > 0) { + const input = formGroup.find('input').first() + if (typeof input !== undefined && input.length > 0) { + $(input).val($(input).val().replace(/\|/g, ',')) + } + } + } + } + } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/tags.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/tags.js new file mode 100644 index 000000000..9c0b07eca --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/tags.js @@ -0,0 +1,20 @@ +import { readConf, writeconf, semanticConf } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_TAGS_LABEL'), + name: 'tags', + attrs: { type: 'tags' }, + icon: '' + }, + attributes: { + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, + read: readConf, + write: writeconf, + semantic: semanticConf + }, + // disabledAttributes: [], + renderInput(fieldData) { + return { field: '' } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/text.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/text.js new file mode 100644 index 000000000..d4f2ac6c0 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/text.js @@ -0,0 +1,48 @@ +import { readConf, writeconf, semanticConf } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_TEXT_LABEL'), + name: 'text', + attrs: { type: 'text' }, + icon: + '' + }, + attributes: { + size: { label: _t('BAZ_FORM_EDIT_TEXT_SIZE'), value: '' }, + maxlength: { label: _t('BAZ_FORM_EDIT_TEXT_MAX_LENGTH'), value: '' }, + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, + separator: { label: '' }, // separate important attrs from others + subtype: { + label: _t('BAZ_FORM_EDIT_TEXT_TYPE_LABEL'), + options: { + text: _t('BAZ_FORM_EDIT_TEXT_TYPE_TEXT'), + number: _t('BAZ_FORM_EDIT_TEXT_TYPE_NUMBER'), + range: _t('BAZ_FORM_EDIT_TEXT_TYPE_RANGE'), + url: _t('BAZ_FORM_EDIT_TEXT_TYPE_URL'), + password: _t('BAZ_FORM_EDIT_TEXT_TYPE_PASSWORD'), + color: _t('BAZ_FORM_EDIT_TEXT_TYPE_COLOR') + } + }, + read: readConf, + write: writeconf, + semantic: semanticConf, + pattern: { + label: _t('BAZ_FORM_EDIT_TEXT_PATTERN'), + value: '', + placeholder: `${_t('BAZ_FORM_EDIT_ADVANCED_MODE')} Ex: [0-9]+ ou [A-Za-z]{3}, ...` + } + }, + // disabledAttributes: [], + renderInput(fieldData) { + let string = `` + } else if (fieldData.subtype == 'range' || fieldData.subtype == 'number') { + string += ` min="${fieldData.size || ''}" max="${fieldData.maxlength || ''}"/>` + } else { + string += ` value="${fieldData.value}"/>` + } + return { field: string } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js new file mode 100644 index 000000000..950d6bc32 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js @@ -0,0 +1,33 @@ +import { readConf, writeconf, semanticConf, defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_TEXTAREA_LABEL'), + name: 'textarea', + attrs: { type: 'textarea' }, + icon: '' + }, + attributes: { + syntax: { + label: _t('BAZ_FORM_EDIT_TEXTAREA_SYNTAX_LABEL'), + options: { + wiki: 'Wiki', + html: _t('BAZ_FORM_EDIT_TEXTAREA_SYNTAX_HTML'), + nohtml: _t('BAZ_FORM_EDIT_TEXTAREA_SYNTAX_NOHTML') + } + }, + hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, + size: { label: _t('BAZ_FORM_EDIT_TEXTAREA_SIZE_LABEL'), value: '' }, + rows: { + label: _t('BAZ_FORM_EDIT_TEXTAREA_ROWS_LABEL'), + type: 'number', + placeholder: _t('BAZ_FORM_EDIT_TEXTAREA_ROWS_PLACEHOLDER') + }, + read: readConf, + write: writeconf, + semantic: semanticConf + }, + // disabledAttributes: [], + attributesMapping: { ...defaultMapping, ...{ 4: 'rows', 7: 'syntax' } }, + // renderInput(fieldData) {}, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/titre.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/titre.js new file mode 100644 index 000000000..fb9c075f5 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/titre.js @@ -0,0 +1,14 @@ +export default { + field: { + label: _t('BAZ_FORM_EDIT_TITLE_LABEL'), + name: 'titre', + attrs: { type: 'titre' }, + icon: '' + }, + attributes: {}, + // disabledAttributes: [], + attributesMapping: { 0: 'type', 1: 'value', 2: 'label' }, + renderInput(field) { + return { field: field.value } + }, +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/url.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/url.js new file mode 100644 index 000000000..d5ccd7da7 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/url.js @@ -0,0 +1,19 @@ +import { readConf, writeconf, semanticConf, defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_URL_LABEL'), + name: 'url', + attrs: { type: 'url' }, + icon: '' + }, + attributes: { + read: readConf, + write: writeconf, + semantic: semanticConf + }, + // disabledAttributes: [], + renderInput(fieldData) { + return { field: `` } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/utilisateur_wikini.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/utilisateur_wikini.js new file mode 100644 index 000000000..72c2b5501 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/utilisateur_wikini.js @@ -0,0 +1,52 @@ +import renderHelper from './commons/render-helper.js' +import { defaultMapping } from './commons/attributes.js' + +export default { + field: { + label: _t('BAZ_FORM_EDIT_USERS_WIKINI_LABEL'), + name: 'utilisateur_wikini', + attrs: { type: 'utilisateur_wikini' }, + icon: '' + }, + attributes: { + name_field: { label: _t('BAZ_FORM_EDIT_USERS_WIKINI_NAME_FIELD_LABEL'), value: 'bf_titre' }, + email_field: { + label: _t('BAZ_FORM_EDIT_USERS_WIKINI_EMAIL_FIELD_LABEL'), + value: 'bf_mail' + }, + // mailing_list: { + // label: "Inscrite à une liste de diffusion" + // }, + autoupdate_email: { + label: _t('BAZ_FORM_EDIT_USERS_WIKINI_AUTOUPDATE_MAIL'), + options: { 0: _t('NO'), 1: _t('YES') } + }, + auto_add_to_group: { + label: _t('BAZ_FORM_EDIT_ADD_TO_GROUP_LABEL'), + value: '', + placeholder: _t('BAZ_FORM_EDIT_ADD_TO_GROUP_DESCRIPTION'), + description: _t('BAZ_FORM_EDIT_ADD_TO_GROUP_DESCRIPTION') + } + }, + // disabledAttributes: [], + attributesMapping: { + ...defaultMapping, + ...{ + 0: 'type', + 1: 'name_field', + 2: 'email_field', + 5: '', /* 5:"mailing_list", */ + 6: 'auto_add_to_group', + 8: '', + 9: 'autoupdate_email' + } + }, + renderInput(field) { + return { + field: '', + onRender() { + renderHelper.defineLabelHintForGroup(field, 'auto_add_to_group', _t('BAZ_FORM_EDIT_ADD_TO_GROUP_HELP')) + } + } + } +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js new file mode 100644 index 000000000..6370c5a26 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js @@ -0,0 +1,531 @@ +import text from './fields/text.js' +import textarea from './fields/textarea.js' +import date from './fields/date.js' +import image from './fields/image.js' +import url from './fields/url.js' +import file from './fields/file.js' +import champs_mail from './fields/champs_mail.js' +import select from './fields/select.js' +import checkbox_group from './fields/checkbox-group.js' +import radio_group from './fields/radio-group.js' +import map from './fields/map.js' +import tags from './fields/tags.js' +import labelhtml from './fields/labelhtml.js' +import titre from './fields/titre.js' +import bookmarklet from './fields/bookmarklet.js' +import conditionschecking from './fields/conditionschecking.js' +import calc from './fields/calc.js' +import reactions from './fields/reactions.js' +import inscriptionliste from './fields/inscriptionliste.js' +import utilisateur_wikini from './fields/utilisateur_wikini.js' +import acls from './fields/acls.js' +import metadatas from './fields/metadatas.js' +import listefichesliees from './fields/listefichesliees.js' +import custom from './fields/custom.js' +import tabs from './fields/tabs.js' +import tabchange from './fields/tabchange.js' + +import { defaultMapping } from './fields/commons/attributes.js' + +const $formBuilderTextInput = $('#form-builder-text') +let formBuilder + +const fields = { + text, textarea, date, image, url, file, champs_mail, select, + 'checkbox-group': checkbox_group, 'radio-group': radio_group, + map, tags, labelhtml, titre, bookmarklet, conditionschecking, calc, + reactions, inscriptionliste, utilisateur_wikini, acls, metadatas, + listefichesliees, custom, tabs, tabchange +} + +function mapFieldsConf(callback) { + return Object.fromEntries( + Object.entries(fields).map(([name, conf]) => [name, callback(conf)]) + .filter(([name, conf]) => !!conf) + ) +} + +// Define an entire group of fields to be added to the stage at a time. +const inputSets = [ + { + label: _t('BAZ_FORM_EDIT_TABS'), + name: 'tabs', + icon: '', + fields: [ + { + type: 'tabs', + label: _t('BAZ_FORM_EDIT_TABS') + }, + { + type: 'tabchange', + label: _t('BAZ_FORM_EDIT_TABCHANGE') + }, + { + type: 'tabchange', + label: _t('BAZ_FORM_EDIT_TABCHANGE') + }, + { + type: 'tabchange', + label: _t('BAZ_FORM_EDIT_TABCHANGE') + } + ] + }, + { + label: _t('BAZ_FORM_EDIT_CONDITIONCHECKING_LABEL'), + name: 'conditionschecking', + icon: '', + fields: [ + { + type: 'conditionschecking', + label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL') + }, + { + type: 'labelhtml', + label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_END'), + content_saisie: `
` + } + ] + } +] + +const yesWikiMapping = mapFieldsConf((conf) => conf.attributesMapping || defaultMapping) + +// Mapping betwwen yeswiki field type and standard field implemented by form builder +const yesWikiTypes = { + lien_internet: { type: 'url' }, + lien_internet_bis: { type: 'text', subtype: 'url' }, + mot_de_passe: { type: 'text', subtype: 'password' }, + // "nombre": { type: "text", subtype: "tel" }, + texte: { type: 'text' }, // all other type text subtype (range, text, tel) + textelong: { type: 'textarea', subtype: 'textarea' }, + listedatedeb: { type: 'date' }, + listedatefin: { type: 'date' }, + jour: { type: 'date' }, + map: { type: 'map' }, + carte_google: { type: 'map' }, + checkbox: { type: 'checkbox-group', subtype2: 'list' }, + liste: { type: 'select', subtype2: 'list' }, + radio: { type: 'radio-group', subtype2: 'list' }, + checkboxfiche: { type: 'checkbox-group', subtype2: 'form' }, + listefiche: { type: 'select', subtype2: 'form' }, + radiofiche: { type: 'radio-group', subtype2: 'form' }, + fichier: { type: 'file', subtype: 'file' }, + champs_cache: { type: 'hidden' }, + listefiches: { type: 'listefichesliees' } +} + +const defaultFieldsName = { + textarea: 'bf_description', + image: 'bf_image', + champs_mail: 'bf_mail', + date: 'bf_date_debut_evenement' +} + +const I18nOption = { + ar: 'ar-SA', + ca: 'ca-ES', + cs: 'cs-CZ', + da: 'da-DK', + de: 'de-DE', + el: 'el-GR', + en: 'en-US', + es: 'es-ES', + fa: 'fa-IR', + fi: 'fi-FI', + fr: 'fr-FR', + he: 'he-IL', + hu: 'hu-HU', + it: 'it-IT', + ja: 'ja-JP', + my: 'my-MM', + nb: 'nb-NO', + pl: 'pl-PL', + pt: 'pt-BR', + qz: 'qz-MM', + ro: 'ro-RO', + ru: 'ru-RU', + sj: 'sl-SL', + th: 'th-TH', + uk: 'uk-UA', + vi: 'vi-VN', + zh: 'zh-CN' +} + +function copyMultipleSelectValues(currentField) { + const currentId = $(currentField).prop('id') + // based on formBuilder/Helpers.js 'incrementId' function + const split = currentId.lastIndexOf('-') + const clonedFieldNumber = parseInt(currentId.substring(split + 1)) - 1 + const baseString = currentId.substring(0, split) + const clonedId = `${baseString}-${clonedFieldNumber}` + + // find cloned field + const clonedField = $(`#${clonedId}`) + if (clonedField.length > 0) { + // copy multiple select + const clonedFieldSelects = $(clonedField).find('select[multiple=true]') + clonedFieldSelects.each(function() { + const currentSelect = $(currentField).find(`select[multiple=true][name=${$(this).prop('name')}]`) + currentSelect.val($(this).val()) + }) + } +} + +const typeUserEvents = {} +Object.keys(fields).forEach((field) => { + typeUserEvents[field] = { onclone: copyMultipleSelectValues } +}) + +function initializeFormbuilder() { + // FormBuilder conf + formBuilder = $('#form-builder-container').formBuilder({ + showActionButtons: false, + fields: Object.values(fields).map((conf) => conf.field).filter((f) => !!f), + controlOrder: Object.keys(fields), + typeUserAttrs: mapFieldsConf((conf) => conf.attributes), + typeUserDisabledAttrs: mapFieldsConf((conf) => conf.disabledAttributes), + typeUserEvents, + inputSets, + templates: mapFieldsConf((conf) => conf.renderInput), + i18n: { + locale: I18nOption[wiki.locale] ?? 'fr-FR', + location: `${wiki.baseUrl.replace('?', '')}javascripts/vendor/formbuilder-languages/` + }, + // Disable some default fields of Jquery formBuilder + disableFields: [ + 'number', + 'button', + 'autocomplete', + 'checkbox', + 'paragraph', + 'header', + 'textarea', + 'checkbox-group', + 'radio-group', + 'select', + 'hidden' + ], + // disbale some default attributes of Jquery formBuilder for all fields + disabledAttrs: [ + 'access', + 'placeholder', + 'className', + 'inline', + 'toggle', + 'description', + 'other', + 'multiple' + ], + onAddField(fieldId, field) { + if (!field.hasOwnProperty('read')) { + field.read = [' * ']// everyone by default + } + if (!field.hasOwnProperty('write')) { + field.write = (field.type === 'champs_mail') + ? [' % '] // owner and @admins by default for e-mail + : [' * '] // everyone by default + } + if (field.type === 'acls' && !field.hasOwnProperty('comment')) { + field.comment = ['comments-closed']// comments-closed by default + } + if (field.type === 'champs_mail' && !('seeEmailAcls' in field)) { + field.seeEmailAcls = [' % ']// owner and @admins by default + } + } + }) + + // disable bf_titre identifier + $('.fld-name').each(function() { + if ($(this).val() === 'bf_titre') { + $(this).attr('disabled', true) + } + }) + + // Each 300ms update the text field converting form bulder content into wiki syntax + let formBuilderInitialized = false + let existingFieldsNames = []; let + existingFieldsIds = [] + + setInterval(() => { + if (!formBuilder || !formBuilder.actions || !formBuilder.actions.setData) return + if (!formBuilderInitialized) { + initializeBuilderFromTextInput() + existingFieldsIds = getFieldsIds() + formBuilderInitialized = true + } + if ($formBuilderTextInput.is(':focus')) return + // Change names + $('.form-group.name-wrap label').text(_t('BAZ_FORM_EDIT_UNIQUE_ID')) + $('.form-group.label-wrap label').text(_t('BAZ_FORM_EDIT_NAME')) + existingFieldsNames = [] + $('.fld-name').each(function() { existingFieldsNames.push($(this).val()) }) + + // Transform input[textarea] in real textarea + $('input[type="textarea"]').replaceWith(function() { + const domTextarea = document.createElement('textarea') + domTextarea.id = this.id + domTextarea.name = this.name + domTextarea.value = this.value + domTextarea.classList = this.classList + domTextarea.title = this.title + domTextarea.rows = $(this).attr('rows') + return domTextarea + }) + + // Slugiy field names + $('.fld-name').each(function() { + const newValue = $(this) + .val() + .replace(/[^a-z^A-Z^_^0-9^{^}]/g, '_') + .toLowerCase() + $(this).val(newValue) + }) + + if ($('#form-builder-container').is(':visible')) { + const formData = formBuilder.actions.getData() + const wikiText = formatJsonDataIntoWikiText(formData) + if (wikiText) $formBuilderTextInput.val(wikiText) + } + + // when selecting between data source lists or forms, we need to populate again the listOfFormId select with the + // proper set of options + $('.radio-group-field, .checkbox-group-field, .select-field') + .find('select[name=subtype2]:not(.initialized)') + .change(function() { + $(this).addClass('initialized') + const visibleSelect = $(this) + .closest('.form-field') + .find('select[name=listeOrFormId]') + selectedValue = visibleSelect.val() + visibleSelect.empty() + const optionToAddToSelect = $(this) + .closest('.form-field') + .find(`select[name=${$(this).val()}Id] option`) + visibleSelect.append(new Option('', '', false)) + optionToAddToSelect.each(function() { + const optionKey = $(this).attr('value') + const optionLabel = $(this).text() + const isSelected = optionKey == selectedValue + const newOption = new Option(optionLabel, optionKey, false, isSelected) + visibleSelect.append(newOption) + }) + }) + .trigger('change') + + $('.fld-name').each(function() { + let name = $(this).val() + const id = $(this).closest('.form-field').attr('id') + + // Detect new fields added + if (!existingFieldsIds.includes(id)) { + const fieldType = $(this).closest('.form-field').attr('type') + + // Make the default names easier to read + if (['radio_group', 'checkbox_group', 'select'].includes(fieldType)) { + name = '' + } else if (!name.includes('bf_')) { + name = defaultFieldsName[fieldType] || `bf_${fieldType}` + if (existingFieldsNames.includes(name)) { + // If name already exist, we add a number (bf_address, bf_address1, bf_address2...) + number = 1 + while (existingFieldsNames.includes(name + number)) number += 1 + name += number + } + } + + // if it's a map, we automatically add a bf_addresse + if (fieldType == 'map' && !existingFieldsNames.includes('bf_adresse')) { + const field = { + type: 'text', + subtype: 'text', + name: 'bf_adresse', + label: _t('BAZ_FORM_EDIT_ADDRESS') + } + const index = $(this).closest('.form-field').index() + formBuilder.actions.addField(field, index) + } + } + $(this).val(name) + }) + + existingFieldsIds = getFieldsIds() + + $('.text-field select[name=subtype]:not(.initialized)').on('change', function() { + $(this).addClass('initialized') + const $parent = $(this).closest('.form-field') + if ($(this).val() == 'range' || $(this).val() == 'number') { + $parent.find('.maxlength-wrap label').text(_t('BAZ_FORM_EDIT_MAX_VAL')) + $parent.find('.size-wrap label').text(_t('BAZ_FORM_EDIT_MIN_VAL')) + } else { + $parent.find('.maxlength-wrap label').text(_t('BAZ_FORM_EDIT_MAX_LENGTH')) + $parent.find('.size-wrap label').text(_t('BAZ_FORM_EDIT_NB_CHARS')) + } + if ($(this).val() == 'color') { + $parent.find('.maxlength-wrap, .size-wrap').hide() + } else { + $parent.find('.maxlength-wrap, .size-wrap').show() + } + }) + .trigger('change') + + // in semantic field, we want to separate value by coma + $('.fld-semantic').each(function() { + let newVal = $(this) + .val() + .replace(/\s*,\s*/g, ',') + newVal = newVal.replace(/\s+/g, ',') + newVal = newVal.replace(/,+/g, ',') + $(this).val(newVal) + }) + + // Changes icons and icones helpers + $('a[type=remove].icon-cancel') + .removeClass('icon-cancel') + .html('') + $('a[type=copy].icon-copy').attr('title', _t('DUPLICATE')) + $('a[type=edit].icon-pencil').attr('title', _t('BAZ_FORM_EDIT_HIDE')) + }, 300) + + $('#formbuilder-link').click(initializeBuilderFromTextInput) +} + +document.addEventListener("DOMContentLoaded", function() { + initializeFormbuilder(); +}) + +function getFieldsIds() { + let result = [] + $('.fld-name').each(function() { result.push($(this).closest('.form-field').attr('id')) }) + return result +} + +// Remove accidental br at the end of the labels +function removeBR(text) { + let newValue = text.replace(/(

<\/div>)+$/g, '') + // replace multiple '

' when at the end of the value + newValue = newValue.replace(/(
)+$/g, '') + // replace multiple '
' when at the end of the value + return newValue +} + +function initializeBuilderFromTextInput() { + const jsonData = parseWikiTextIntoJsonData($formBuilderTextInput.val()) + formBuilder.actions.setData(JSON.stringify(jsonData)) +} + +// transform a json object like "{ type: 'texte', name: 'bf_titre', label: 'Nom' .... }" +// into wiki text like "texte***bf_titre***Nom***255***255*** *** *** ***1***0***" +function formatJsonDataIntoWikiText(formData) { + if (formData.length == 0) return null + let wikiText = '' + + for (let i = 0; i < formData.length; i++) { + const wikiProps = {} + const formElement = formData[i] + const mapping = yesWikiMapping[formElement.type] + + for (const type in yesWikiTypes) { + if ( + formElement.type == yesWikiTypes[type].type + && (!formElement.subtype + || !yesWikiTypes[type].subtype + || formElement.subtype == yesWikiTypes[type].subtype) + && (!formElement.subtype2 + || formElement.subtype2 == yesWikiTypes[type].subtype2) + ) { + wikiProps[0] = type + break + } + } + // for non mapped fields, we just keep the form type + if (!wikiProps[0]) wikiProps[0] = formElement.type + + // fix for url field which can be build with textField or urlField + if (wikiProps[0]) wikiProps[0] = wikiProps[0].replace('_bis', '') + + for (const key in mapping) { + const property = mapping[key] + if (property != 'type') { + let value = formElement[property] + if (['required', 'access'].indexOf(property) > -1) value = value ? '1' : '0' + if (property == 'label') { + wikiProps[key] = removeBR(value).replace(/\n$/gm, '') + } else { + wikiProps[key] = value + } + } + } + + const maxProp = Math.max.apply(Math, Object.keys(wikiProps)) + for (let j = 0; j <= maxProp; j++) { + wikiText += wikiProps[j] || ' ' + wikiText += '***' + } + wikiText += '\n' + } + return wikiText +} + +// transform text with wiki text like "texte***bf_titre***Nom***255***255*** *** *** ***1***0***" +// into a json object "{ type: 'texte', name: 'bf_titre', label: 'Nom' .... }" +function parseWikiTextIntoJsonData(text) { + const result = [] + var text = text.trim() + const textFields = text.split('\n') + for (let i = 0; i < textFields.length; i++) { + const textField = textFields[i] + const fieldValues = textField.split('***') + const fieldObject = {} + if (fieldValues.length > 1) { + const wikiType = fieldValues[0] + let fieldType = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].type : wikiType + // check that the fieldType really exists in our form builder + if (!(fieldType in yesWikiMapping)) fieldType = 'custom' + + const mapping = yesWikiMapping[fieldType] + + fieldObject.type = fieldType + fieldObject.subtype = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].subtype : '' + fieldObject.subtype2 = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].subtype2 : '' + const start = fieldType == 'custom' ? 0 : 1 + for (let j = start; j < fieldValues.length; j++) { + let value = fieldValues[j] + const field = mapping && j in mapping ? mapping[j] : j + if (field == 'required') value = value == '1' + if (field) { + if (field == 'read' || field == 'write' || field == 'comment') { + fieldObject[field] = (value.trim() === '') + ? ( + field == 'comment' + ? [' + '] + : [' * '] + ) + : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) + } else if (field == 'seeEmailAcls') { + fieldObject[field] = (value.trim() === '') + ? ' % ' // if not define in tempalte, choose owner and admins + : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) + } else { + fieldObject[field] = value + } + } + } + if (!fieldObject.label) { + fieldObject.label = wikiType + for (let k = 0; k < fields.length; k++) if (fields[k].name == wikiType) fieldObject.label = fields[k].label + } + result.push(fieldObject) + } + } + if (wiki.isDebugEnabled) { + console.log('parse result', result) + } + return result +} + +$('a[href="#formbuilder"]').on('click', (event) => { + if (!confirm(_t('BAZ_FORM_EDIT_CONFIRM_DISPLAY_FORMBUILDER'))) { + event.preventDefault() + return false + } +}) diff --git a/tools/bazar/templates/forms/_forms_form_js_include.twig b/tools/bazar/templates/forms/_forms_form_js_include.twig index 0e2278164..e0ff4af10 100644 --- a/tools/bazar/templates/forms/_forms_form_js_include.twig +++ b/tools/bazar/templates/forms/_forms_form_js_include.twig @@ -1,6 +1,3 @@ {{ include_javascript('javascripts/vendor/jquery-ui-sortable/jquery-ui.min.js') }} {{ include_javascript('javascripts/vendor/formBuilder/form-builder.min.js') }} -{{ include_javascript('tools/bazar/presentation/javascripts/form-edit-template-helper.js') }} -{{ include_javascript('tools/bazar/presentation/javascripts/form-edit-template.js') }} -{{ include_javascript('tools/bazar/presentation/javascripts/conditionschecking/form-edit-template.js') }} -{{ include_javascript('tools/bazar/presentation/javascripts/calcfield/form-edit-template.js') }} \ No newline at end of file +{{ include_javascript('tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js', false, true) }} \ No newline at end of file diff --git a/tools/bazar/templates/forms/forms_form.twig b/tools/bazar/templates/forms/forms_form.twig index 02e8a747c..73f8ef227 100644 --- a/tools/bazar/templates/forms/forms_form.twig +++ b/tools/bazar/templates/forms/forms_form.twig @@ -208,7 +208,4 @@ diff --git a/tools/bazar/templates/inputs/conditions-checking.twig b/tools/bazar/templates/inputs/conditions-checking.twig index f1397fb75..da5ca0ae2 100644 --- a/tools/bazar/templates/inputs/conditions-checking.twig +++ b/tools/bazar/templates/inputs/conditions-checking.twig @@ -1,2 +1,2 @@ -{{ include_javascript('tools/bazar/presentation/javascripts/conditionschecking/conditionschecking.js') }} +{{ include_javascript('tools/bazar/presentation/javascripts/bazar-fields/conditionschecking.js') }}
\ No newline at end of file From a79b91f56c2c80ca45e07a21a081c04033b89d86 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 014/152] feat(form-builder): move style form margot and improve UI --- .../styles/form-edit-template.css | 88 ++++++++++++++++--- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/tools/bazar/presentation/styles/form-edit-template.css b/tools/bazar/presentation/styles/form-edit-template.css index 2480ef371..b15f84fdb 100644 --- a/tools/bazar/presentation/styles/form-edit-template.css +++ b/tools/bazar/presentation/styles/form-edit-template.css @@ -1,4 +1,79 @@ +.form-builder-label { + display: none; +} +#form-builder-container { + height: auto !important; + padding: 0 0 1px 0 !important; +} +#form-builder-container .form-control { + background-color: white; + border: 1px solid #ccc; +} +#form-builder-container .form-elements { + padding: 1rem; +} +#form-builder-container .form-elements .form-group { + margin: 0 0 5px 0; +} +#form-builder-container .form-elements .form-group label { + font-size: .8em; + width: auto !important; + text-align: left; + float: none; + padding: 0; + overflow: revert; +} +#form-builder-container .form-wrap.form-builder .stage-wrap { + width: 74%; +} +#form-builder-container .form-wrap.form-builder .frmb-control li { + border-radius: 0; + border-right: none; +} +#form-builder-container .form-wrap.form-builder .frmb li { + background-color: transparent; + border-radius: 0; + transition: background-color .3s; +} +#form-builder-container .form-wrap.form-builder .frmb { + margin: 0; +} +#form-builder-container .form-wrap.form-builder .frmb .form-elements .input-wrap { + float: none; + margin-left: 0; + width: 100%; +} +#form-builder-container .form-wrap.form-builder .frmb > li:hover { + background-color: var(--neutral-light-color); + border: none; + box-shadow: none; +} +#form-builder-container .form-wrap.form-builder .frmb .form-elements .input-wrap>input[type='checkbox'] { + margin: 0; +} +/* Remove checkbox style inside form-builder*/ +#form-builder-container [type="checkbox"]:not(:checked), +#form-builder-container [type="checkbox"]:checked { + position: relative; + opacity: 1; + pointer-events: initial; +} + +#form-builder-container .required-wrap { + display: flex; + align-items: center +} +#form-builder-container .required-wrap label { + order: 2; +} +#form-builder-container .required-wrap .input-wrap { + margin-right: .5rem; + order: 1; + width: auto !important; +} + .form-field { overflow: hidden; } + /* Order of the field attributes */ .form-wrap.form-builder .frmb .form-elements { display: flex; flex-direction: column; } .form-wrap.form-builder .frmb .form-field .form-group { order: 100; } @@ -23,9 +98,6 @@ padding-top: 10px; } -.form-wrap.form-builder .frmb .form-elements .input-wrap { width: 70%; } -.form-wrap.form-builder .frmb .form-elements .false-label:first-child, .form-wrap.form-builder .frmb .form-elements label:first-child { width: 25%; } - .titre-field .required-wrap, .titre-field .name-wrap { display: none !important; @@ -123,16 +195,12 @@ xmp { overflow: auto; } } /* Make the list of fields smaller */ -.form-wrap.form-builder .frmb-control li { - padding: 5px 10px; - font-size: 15px; -} - .form-wrap.form-builder .frmb li { - padding: 5px 10px; + padding: 5px 1rem 8px 1rem !important; + margin: 0 !important; } .form-wrap.form-builder .frmb li:first-child { - padding-top: 10px; + padding-top: 10px !important; } .form-wrap.form-builder .frmb .field-label { From 4a42c8315b092c8b2178b19a59da39b5ec9a3f25 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 015/152] feat(form-builder): ability to add custom field from an extension --- .../form-edit-template/fields/README | 27 ++++++++- .../fields/conditionschecking.js | 17 ++++++ .../form-edit-template/fields/tabs.js | 24 ++++++++ .../form-edit-template/form-edit-template.js | 60 ++++--------------- .../forms/_forms_form_js_include.twig | 3 - tools/bazar/templates/forms/forms_form.twig | 5 +- 6 files changed, 82 insertions(+), 54 deletions(-) delete mode 100644 tools/bazar/templates/forms/_forms_form_js_include.twig diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/README b/tools/bazar/presentation/javascripts/form-edit-template/fields/README index 59673b003..c1725289b 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/README +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/README @@ -1,5 +1,28 @@ export default { + field: { + label: 'New Field', + name: 'newfield', + attrs: { type: 'newfield' }, + icon: '' + // icon: '' + }, + // Any special attributes + attributes: { + my_attribute: { label: 'Specific attribute', value: '' }, + }, + // Disable default attributes from jQuery formBuilder + disabledAttributes: [], + // How to render the input + renderInput(fieldData) { + return { field: '' } + }, +} + +// How to add a field from an extension + +window.formBuilderFields.newfield = { + // field config, see above + field: {}, attributes: {}, - // disabledAttributes: [], - // renderInput(fieldData) {}, + renderInput() {} } diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/conditionschecking.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/conditionschecking.js index 4291750eb..f4d5577e0 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/conditionschecking.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/conditionschecking.js @@ -8,6 +8,23 @@ export default { attrs: { type: 'conditionschecking' }, icon: '' }, + // Define an entire group of fields to be added to the stage at a time. + set: { + label: _t('BAZ_FORM_EDIT_CONDITIONCHECKING_LABEL'), + name: 'conditionschecking', + icon: '', + fields: [ + { + type: 'conditionschecking', + label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL') + }, + { + type: 'labelhtml', + label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_END'), + content_saisie: `
` + } + ] + }, attributes: { condition: { label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL'), diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/tabs.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/tabs.js index 3ce3d8c78..af02e67c2 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/tabs.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/tabs.js @@ -8,6 +8,30 @@ export default { attrs: { type: 'tabs' }, icon: '' }, + // Define an entire group of fields to be added to the stage at a time. + set: { + label: _t('BAZ_FORM_EDIT_TABS'), + name: 'tabs', + icon: '', + fields: [ + { + type: 'tabs', + label: _t('BAZ_FORM_EDIT_TABS') + }, + { + type: 'tabchange', + label: _t('BAZ_FORM_EDIT_TABCHANGE') + }, + { + type: 'tabchange', + label: _t('BAZ_FORM_EDIT_TABCHANGE') + }, + { + type: 'tabchange', + label: _t('BAZ_FORM_EDIT_TABCHANGE') + } + ] + }, attributes: { formTitles: { label: _t('BAZ_FORM_EDIT_TABS_FOR_FORM'), diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js index 6370c5a26..485d21165 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js @@ -30,7 +30,8 @@ import { defaultMapping } from './fields/commons/attributes.js' const $formBuilderTextInput = $('#form-builder-text') let formBuilder -const fields = { +// Use window to make it available outside of module, so extension can adds their own fields +window.formBuilderFields = { text, textarea, date, image, url, file, champs_mail, select, 'checkbox-group': checkbox_group, 'radio-group': radio_group, map, tags, labelhtml, titre, bookmarklet, conditionschecking, calc, @@ -40,58 +41,21 @@ const fields = { function mapFieldsConf(callback) { return Object.fromEntries( - Object.entries(fields).map(([name, conf]) => [name, callback(conf)]) + Object.entries(formBuilderFields).map(([name, conf]) => [name, callback(conf)]) .filter(([name, conf]) => !!conf) ) } // Define an entire group of fields to be added to the stage at a time. -const inputSets = [ - { - label: _t('BAZ_FORM_EDIT_TABS'), - name: 'tabs', - icon: '', - fields: [ - { - type: 'tabs', - label: _t('BAZ_FORM_EDIT_TABS') - }, - { - type: 'tabchange', - label: _t('BAZ_FORM_EDIT_TABCHANGE') - }, - { - type: 'tabchange', - label: _t('BAZ_FORM_EDIT_TABCHANGE') - }, - { - type: 'tabchange', - label: _t('BAZ_FORM_EDIT_TABCHANGE') - } - ] - }, - { - label: _t('BAZ_FORM_EDIT_CONDITIONCHECKING_LABEL'), - name: 'conditionschecking', - icon: '', - fields: [ - { - type: 'conditionschecking', - label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_LABEL') - }, - { - type: 'labelhtml', - label: _t('BAZ_FORM_EDIT_CONDITIONS_CHECKING_END'), - content_saisie: `
` - } - ] - } -] +// Use window to make it available outside of module, so extension can adds their own fields +window.inputSets = Object.values(formBuilderFields).map((conf) => conf.set).filter((f) => !!f) -const yesWikiMapping = mapFieldsConf((conf) => conf.attributesMapping || defaultMapping) +// Use window to make it available outside of module, so extension can adds their own fields +window.yesWikiMapping = mapFieldsConf((conf) => conf.attributesMapping || defaultMapping) // Mapping betwwen yeswiki field type and standard field implemented by form builder -const yesWikiTypes = { +// Use window to make it available outside of module, so extension can adds their own fields +window.yesWikiTypes = { lien_internet: { type: 'url' }, lien_internet_bis: { type: 'text', subtype: 'url' }, mot_de_passe: { type: 'text', subtype: 'password' }, @@ -172,7 +136,7 @@ function copyMultipleSelectValues(currentField) { } const typeUserEvents = {} -Object.keys(fields).forEach((field) => { +Object.keys(formBuilderFields).forEach((field) => { typeUserEvents[field] = { onclone: copyMultipleSelectValues } }) @@ -180,8 +144,8 @@ function initializeFormbuilder() { // FormBuilder conf formBuilder = $('#form-builder-container').formBuilder({ showActionButtons: false, - fields: Object.values(fields).map((conf) => conf.field).filter((f) => !!f), - controlOrder: Object.keys(fields), + fields: Object.values(formBuilderFields).map((conf) => conf.field).filter((f) => !!f), + controlOrder: Object.keys(formBuilderFields), typeUserAttrs: mapFieldsConf((conf) => conf.attributes), typeUserDisabledAttrs: mapFieldsConf((conf) => conf.disabledAttributes), typeUserEvents, diff --git a/tools/bazar/templates/forms/_forms_form_js_include.twig b/tools/bazar/templates/forms/_forms_form_js_include.twig deleted file mode 100644 index e0ff4af10..000000000 --- a/tools/bazar/templates/forms/_forms_form_js_include.twig +++ /dev/null @@ -1,3 +0,0 @@ -{{ include_javascript('javascripts/vendor/jquery-ui-sortable/jquery-ui.min.js') }} -{{ include_javascript('javascripts/vendor/formBuilder/form-builder.min.js') }} -{{ include_javascript('tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js', false, true) }} \ No newline at end of file diff --git a/tools/bazar/templates/forms/forms_form.twig b/tools/bazar/templates/forms/forms_form.twig index 73f8ef227..4753d35fd 100644 --- a/tools/bazar/templates/forms/forms_form.twig +++ b/tools/bazar/templates/forms/forms_form.twig @@ -1,4 +1,7 @@ -{{ include('@bazar/forms/_forms_form_js_include.twig') }} +{{ include_javascript('javascripts/vendor/jquery-ui-sortable/jquery-ui.min.js') }} +{{ include_javascript('javascripts/vendor/formBuilder/form-builder.min.js') }} +{{ include_javascript('tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js', false, true) }} +{{ include_javascript('tools/bazar/presentation/javascripts/form-edit-template/test-extension.js') }} {{ include_css('tools/bazar/presentation/styles/form-edit-template.css') }}
Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 016/152] refactor(form-builder): continue splitting main file into multiple ones --- .../form-edit-template/fields/README | 2 + .../form-edit-template/fields/champs_mail.js | 1 + .../form-edit-template/fields/date.js | 1 + .../form-edit-template/fields/image.js | 1 + .../form-edit-template/fields/textarea.js | 1 + .../form-edit-template/form-builder-helper.js | 26 ++ .../form-edit-template/form-edit-template.js | 279 +++--------------- .../javascripts/form-edit-template/i18n.js | 30 ++ .../yeswiki-syntax-converter.js | 152 ++++++++++ 9 files changed, 249 insertions(+), 244 deletions(-) create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/i18n.js create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/yeswiki-syntax-converter.js diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/README b/tools/bazar/presentation/javascripts/form-edit-template/fields/README index c1725289b..2ed1f4fd9 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/README +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/README @@ -12,6 +12,8 @@ export default { }, // Disable default attributes from jQuery formBuilder disabledAttributes: [], + // mapping for old yeswiki syntax (field***attribute***other_attribute) + attributesMapping: {}, // How to render the input renderInput(fieldData) { return { field: '' } diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js index a36cab589..b84b48de9 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js @@ -8,6 +8,7 @@ export default { attrs: { type: 'champs_mail' }, icon: '' }, + defaultIdentifier: 'bf_mail', attributes: { hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, separator: { label: '' }, // separate important attrs from others diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js index 3990277f3..e17320863 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js @@ -7,6 +7,7 @@ export default { // attrs: { type: "date" }, // icon: '', // }, + defaultIdentifier: 'bf_date_debut_evenement', attributes: { today_button: { label: _t('BAZ_FORM_EDIT_DATE_TODAY_BUTTON'), diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js index 46a093a7c..1669507fa 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js @@ -7,6 +7,7 @@ export default { attrs: { type: 'image' }, icon: '' }, + defaultIdentifier: 'bf_image', attributes: { hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, thumb_height: { label: _t('BAZ_FORM_EDIT_IMAGE_HEIGHT'), value: '300' }, diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js index 950d6bc32..b4522bdc3 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js @@ -7,6 +7,7 @@ export default { attrs: { type: 'textarea' }, icon: '' }, + defaultIdentifier: 'bf_description', attributes: { syntax: { label: _t('BAZ_FORM_EDIT_TEXTAREA_SYNTAX_LABEL'), diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js b/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js new file mode 100644 index 000000000..cb022f56e --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js @@ -0,0 +1,26 @@ +export function mapFieldsConf(callback) { + return Object.fromEntries( + Object.entries(window.formBuilderFields).map(([name, conf]) => [name, callback(conf)]) + .filter(([name, conf]) => !!conf) + ) +} + +export function copyMultipleSelectValues(currentField) { + const currentId = $(currentField).prop('id') + // based on formBuilder/Helpers.js 'incrementId' function + const split = currentId.lastIndexOf('-') + const clonedFieldNumber = parseInt(currentId.substring(split + 1)) - 1 + const baseString = currentId.substring(0, split) + const clonedId = `${baseString}-${clonedFieldNumber}` + + // find cloned field + const clonedField = $(`#${clonedId}`) + if (clonedField.length > 0) { + // copy multiple select + const clonedFieldSelects = $(clonedField).find('select[multiple=true]') + clonedFieldSelects.each(function() { + const currentSelect = $(currentField).find(`select[multiple=true][name=${$(this).prop('name')}]`) + currentSelect.val($(this).val()) + }) + } +} \ No newline at end of file diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js index 485d21165..05066865d 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js @@ -25,7 +25,9 @@ import custom from './fields/custom.js' import tabs from './fields/tabs.js' import tabchange from './fields/tabchange.js' -import { defaultMapping } from './fields/commons/attributes.js' +import { parseWikiTextIntoJsonData, formatJsonDataIntoWikiText } from './yeswiki-syntax-converter.js' +import { copyMultipleSelectValues, mapFieldsConf } from './form-builder-helper.js' +import I18nOption from './i18n.js' const $formBuilderTextInput = $('#form-builder-text') let formBuilder @@ -39,108 +41,15 @@ window.formBuilderFields = { listefichesliees, custom, tabs, tabchange } -function mapFieldsConf(callback) { - return Object.fromEntries( - Object.entries(formBuilderFields).map(([name, conf]) => [name, callback(conf)]) - .filter(([name, conf]) => !!conf) - ) -} - -// Define an entire group of fields to be added to the stage at a time. -// Use window to make it available outside of module, so extension can adds their own fields -window.inputSets = Object.values(formBuilderFields).map((conf) => conf.set).filter((f) => !!f) - -// Use window to make it available outside of module, so extension can adds their own fields -window.yesWikiMapping = mapFieldsConf((conf) => conf.attributesMapping || defaultMapping) - -// Mapping betwwen yeswiki field type and standard field implemented by form builder -// Use window to make it available outside of module, so extension can adds their own fields -window.yesWikiTypes = { - lien_internet: { type: 'url' }, - lien_internet_bis: { type: 'text', subtype: 'url' }, - mot_de_passe: { type: 'text', subtype: 'password' }, - // "nombre": { type: "text", subtype: "tel" }, - texte: { type: 'text' }, // all other type text subtype (range, text, tel) - textelong: { type: 'textarea', subtype: 'textarea' }, - listedatedeb: { type: 'date' }, - listedatefin: { type: 'date' }, - jour: { type: 'date' }, - map: { type: 'map' }, - carte_google: { type: 'map' }, - checkbox: { type: 'checkbox-group', subtype2: 'list' }, - liste: { type: 'select', subtype2: 'list' }, - radio: { type: 'radio-group', subtype2: 'list' }, - checkboxfiche: { type: 'checkbox-group', subtype2: 'form' }, - listefiche: { type: 'select', subtype2: 'form' }, - radiofiche: { type: 'radio-group', subtype2: 'form' }, - fichier: { type: 'file', subtype: 'file' }, - champs_cache: { type: 'hidden' }, - listefiches: { type: 'listefichesliees' } -} - -const defaultFieldsName = { - textarea: 'bf_description', - image: 'bf_image', - champs_mail: 'bf_mail', - date: 'bf_date_debut_evenement' -} - -const I18nOption = { - ar: 'ar-SA', - ca: 'ca-ES', - cs: 'cs-CZ', - da: 'da-DK', - de: 'de-DE', - el: 'el-GR', - en: 'en-US', - es: 'es-ES', - fa: 'fa-IR', - fi: 'fi-FI', - fr: 'fr-FR', - he: 'he-IL', - hu: 'hu-HU', - it: 'it-IT', - ja: 'ja-JP', - my: 'my-MM', - nb: 'nb-NO', - pl: 'pl-PL', - pt: 'pt-BR', - qz: 'qz-MM', - ro: 'ro-RO', - ru: 'ru-RU', - sj: 'sl-SL', - th: 'th-TH', - uk: 'uk-UA', - vi: 'vi-VN', - zh: 'zh-CN' -} - -function copyMultipleSelectValues(currentField) { - const currentId = $(currentField).prop('id') - // based on formBuilder/Helpers.js 'incrementId' function - const split = currentId.lastIndexOf('-') - const clonedFieldNumber = parseInt(currentId.substring(split + 1)) - 1 - const baseString = currentId.substring(0, split) - const clonedId = `${baseString}-${clonedFieldNumber}` - - // find cloned field - const clonedField = $(`#${clonedId}`) - if (clonedField.length > 0) { - // copy multiple select - const clonedFieldSelects = $(clonedField).find('select[multiple=true]') - clonedFieldSelects.each(function() { - const currentSelect = $(currentField).find(`select[multiple=true][name=${$(this).prop('name')}]`) - currentSelect.val($(this).val()) - }) - } -} +function initializeFormbuilder() { + // Define an entire group of fields to be added to the stage at a time. + const inputSets = Object.values(formBuilderFields).map((conf) => conf.set).filter((f) => !!f) -const typeUserEvents = {} -Object.keys(formBuilderFields).forEach((field) => { - typeUserEvents[field] = { onclone: copyMultipleSelectValues } -}) + const typeUserEvents = {} + Object.keys(formBuilderFields).forEach((field) => { + typeUserEvents[field] = { onclone: copyMultipleSelectValues } + }) -function initializeFormbuilder() { // FormBuilder conf formBuilder = $('#form-builder-container').formBuilder({ showActionButtons: false, @@ -205,10 +114,11 @@ function initializeFormbuilder() { } }) - // Each 300ms update the text field converting form bulder content into wiki syntax + const defaultFieldsName = mapFieldsConf((conf) => conf.defaultIdentifier) + let formBuilderInitialized = false - let existingFieldsNames = []; let - existingFieldsIds = [] + let existingFieldsNames = [] + let existingFieldsIds = [] setInterval(() => { if (!formBuilder || !formBuilder.actions || !formBuilder.actions.setData) return @@ -245,22 +155,23 @@ function initializeFormbuilder() { $(this).val(newValue) }) + // Update the text field converting form builder content into wiki syntax if ($('#form-builder-container').is(':visible')) { const formData = formBuilder.actions.getData() const wikiText = formatJsonDataIntoWikiText(formData) if (wikiText) $formBuilderTextInput.val(wikiText) } - // when selecting between data source lists or forms, we need to populate again the listOfFormId select with the - // proper set of options + // when selecting between data source lists or forms, we need to populate again the + // listOfFormId select with the proper set of options $('.radio-group-field, .checkbox-group-field, .select-field') .find('select[name=subtype2]:not(.initialized)') - .change(function() { + .on('change', function() { $(this).addClass('initialized') const visibleSelect = $(this) .closest('.form-field') .find('select[name=listeOrFormId]') - selectedValue = visibleSelect.val() + const selectedValue = visibleSelect.val() visibleSelect.empty() const optionToAddToSelect = $(this) .closest('.form-field') @@ -291,7 +202,7 @@ function initializeFormbuilder() { name = defaultFieldsName[fieldType] || `bf_${fieldType}` if (existingFieldsNames.includes(name)) { // If name already exist, we add a number (bf_address, bf_address1, bf_address2...) - number = 1 + let number = 1 while (existingFieldsNames.includes(name + number)) number += 1 name += number } @@ -315,22 +226,21 @@ function initializeFormbuilder() { existingFieldsIds = getFieldsIds() $('.text-field select[name=subtype]:not(.initialized)').on('change', function() { - $(this).addClass('initialized') - const $parent = $(this).closest('.form-field') - if ($(this).val() == 'range' || $(this).val() == 'number') { - $parent.find('.maxlength-wrap label').text(_t('BAZ_FORM_EDIT_MAX_VAL')) - $parent.find('.size-wrap label').text(_t('BAZ_FORM_EDIT_MIN_VAL')) - } else { - $parent.find('.maxlength-wrap label').text(_t('BAZ_FORM_EDIT_MAX_LENGTH')) - $parent.find('.size-wrap label').text(_t('BAZ_FORM_EDIT_NB_CHARS')) - } - if ($(this).val() == 'color') { - $parent.find('.maxlength-wrap, .size-wrap').hide() - } else { - $parent.find('.maxlength-wrap, .size-wrap').show() - } - }) - .trigger('change') + $(this).addClass('initialized') + const $parent = $(this).closest('.form-field') + if ($(this).val() == 'range' || $(this).val() == 'number') { + $parent.find('.maxlength-wrap label').text(_t('BAZ_FORM_EDIT_MAX_VAL')) + $parent.find('.size-wrap label').text(_t('BAZ_FORM_EDIT_MIN_VAL')) + } else { + $parent.find('.maxlength-wrap label').text(_t('BAZ_FORM_EDIT_MAX_LENGTH')) + $parent.find('.size-wrap label').text(_t('BAZ_FORM_EDIT_NB_CHARS')) + } + if ($(this).val() == 'color') { + $parent.find('.maxlength-wrap, .size-wrap').hide() + } else { + $parent.find('.maxlength-wrap, .size-wrap').show() + } + }).trigger('change') // in semantic field, we want to separate value by coma $('.fld-semantic').each(function() { @@ -363,130 +273,11 @@ function getFieldsIds() { return result } -// Remove accidental br at the end of the labels -function removeBR(text) { - let newValue = text.replace(/(

<\/div>)+$/g, '') - // replace multiple '

' when at the end of the value - newValue = newValue.replace(/(
)+$/g, '') - // replace multiple '
' when at the end of the value - return newValue -} - function initializeBuilderFromTextInput() { const jsonData = parseWikiTextIntoJsonData($formBuilderTextInput.val()) formBuilder.actions.setData(JSON.stringify(jsonData)) } -// transform a json object like "{ type: 'texte', name: 'bf_titre', label: 'Nom' .... }" -// into wiki text like "texte***bf_titre***Nom***255***255*** *** *** ***1***0***" -function formatJsonDataIntoWikiText(formData) { - if (formData.length == 0) return null - let wikiText = '' - - for (let i = 0; i < formData.length; i++) { - const wikiProps = {} - const formElement = formData[i] - const mapping = yesWikiMapping[formElement.type] - - for (const type in yesWikiTypes) { - if ( - formElement.type == yesWikiTypes[type].type - && (!formElement.subtype - || !yesWikiTypes[type].subtype - || formElement.subtype == yesWikiTypes[type].subtype) - && (!formElement.subtype2 - || formElement.subtype2 == yesWikiTypes[type].subtype2) - ) { - wikiProps[0] = type - break - } - } - // for non mapped fields, we just keep the form type - if (!wikiProps[0]) wikiProps[0] = formElement.type - - // fix for url field which can be build with textField or urlField - if (wikiProps[0]) wikiProps[0] = wikiProps[0].replace('_bis', '') - - for (const key in mapping) { - const property = mapping[key] - if (property != 'type') { - let value = formElement[property] - if (['required', 'access'].indexOf(property) > -1) value = value ? '1' : '0' - if (property == 'label') { - wikiProps[key] = removeBR(value).replace(/\n$/gm, '') - } else { - wikiProps[key] = value - } - } - } - - const maxProp = Math.max.apply(Math, Object.keys(wikiProps)) - for (let j = 0; j <= maxProp; j++) { - wikiText += wikiProps[j] || ' ' - wikiText += '***' - } - wikiText += '\n' - } - return wikiText -} - -// transform text with wiki text like "texte***bf_titre***Nom***255***255*** *** *** ***1***0***" -// into a json object "{ type: 'texte', name: 'bf_titre', label: 'Nom' .... }" -function parseWikiTextIntoJsonData(text) { - const result = [] - var text = text.trim() - const textFields = text.split('\n') - for (let i = 0; i < textFields.length; i++) { - const textField = textFields[i] - const fieldValues = textField.split('***') - const fieldObject = {} - if (fieldValues.length > 1) { - const wikiType = fieldValues[0] - let fieldType = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].type : wikiType - // check that the fieldType really exists in our form builder - if (!(fieldType in yesWikiMapping)) fieldType = 'custom' - - const mapping = yesWikiMapping[fieldType] - - fieldObject.type = fieldType - fieldObject.subtype = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].subtype : '' - fieldObject.subtype2 = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].subtype2 : '' - const start = fieldType == 'custom' ? 0 : 1 - for (let j = start; j < fieldValues.length; j++) { - let value = fieldValues[j] - const field = mapping && j in mapping ? mapping[j] : j - if (field == 'required') value = value == '1' - if (field) { - if (field == 'read' || field == 'write' || field == 'comment') { - fieldObject[field] = (value.trim() === '') - ? ( - field == 'comment' - ? [' + '] - : [' * '] - ) - : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) - } else if (field == 'seeEmailAcls') { - fieldObject[field] = (value.trim() === '') - ? ' % ' // if not define in tempalte, choose owner and admins - : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) - } else { - fieldObject[field] = value - } - } - } - if (!fieldObject.label) { - fieldObject.label = wikiType - for (let k = 0; k < fields.length; k++) if (fields[k].name == wikiType) fieldObject.label = fields[k].label - } - result.push(fieldObject) - } - } - if (wiki.isDebugEnabled) { - console.log('parse result', result) - } - return result -} - $('a[href="#formbuilder"]').on('click', (event) => { if (!confirm(_t('BAZ_FORM_EDIT_CONFIRM_DISPLAY_FORMBUILDER'))) { event.preventDefault() diff --git a/tools/bazar/presentation/javascripts/form-edit-template/i18n.js b/tools/bazar/presentation/javascripts/form-edit-template/i18n.js new file mode 100644 index 000000000..0ff803263 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/i18n.js @@ -0,0 +1,30 @@ +// list of available locales for formBuilder +export default { + ar: 'ar-SA', + ca: 'ca-ES', + cs: 'cs-CZ', + da: 'da-DK', + de: 'de-DE', + el: 'el-GR', + en: 'en-US', + es: 'es-ES', + fa: 'fa-IR', + fi: 'fi-FI', + fr: 'fr-FR', + he: 'he-IL', + hu: 'hu-HU', + it: 'it-IT', + ja: 'ja-JP', + my: 'my-MM', + nb: 'nb-NO', + pl: 'pl-PL', + pt: 'pt-BR', + qz: 'qz-MM', + ro: 'ro-RO', + ru: 'ru-RU', + sj: 'sl-SL', + th: 'th-TH', + uk: 'uk-UA', + vi: 'vi-VN', + zh: 'zh-CN' +} \ No newline at end of file diff --git a/tools/bazar/presentation/javascripts/form-edit-template/yeswiki-syntax-converter.js b/tools/bazar/presentation/javascripts/form-edit-template/yeswiki-syntax-converter.js new file mode 100644 index 000000000..edf347b3e --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/yeswiki-syntax-converter.js @@ -0,0 +1,152 @@ +import { mapFieldsConf } from './form-builder-helper.js' +import { defaultMapping } from './fields/commons/attributes.js' + +// Mapping betwwen yeswiki field type and standard field implemented by form builder +// Use window to make it available outside of module, so extension can adds their own fields +window.yesWikiTypes = { + lien_internet: { type: 'url' }, + lien_internet_bis: { type: 'text', subtype: 'url' }, + mot_de_passe: { type: 'text', subtype: 'password' }, + // "nombre": { type: "text", subtype: "tel" }, + texte: { type: 'text' }, // all other type text subtype (range, text, tel) + textelong: { type: 'textarea', subtype: 'textarea' }, + listedatedeb: { type: 'date' }, + listedatefin: { type: 'date' }, + jour: { type: 'date' }, + map: { type: 'map' }, + carte_google: { type: 'map' }, + checkbox: { type: 'checkbox-group', subtype2: 'list' }, + liste: { type: 'select', subtype2: 'list' }, + radio: { type: 'radio-group', subtype2: 'list' }, + checkboxfiche: { type: 'checkbox-group', subtype2: 'form' }, + listefiche: { type: 'select', subtype2: 'form' }, + radiofiche: { type: 'radio-group', subtype2: 'form' }, + fichier: { type: 'file', subtype: 'file' }, + champs_cache: { type: 'hidden' }, + listefiches: { type: 'listefichesliees' } +} + +function getYesWikiMapping() { + return mapFieldsConf((conf) => conf.attributesMapping || defaultMapping) +} + +// Remove accidental br at the end of the labels +function removeBR(text) { + let newValue = text.replace(/(

<\/div>)+$/g, '') + // replace multiple '

' when at the end of the value + newValue = newValue.replace(/(
)+$/g, '') + // replace multiple '
' when at the end of the value + return newValue +} + +// transform text with wiki text like "texte***bf_titre***Nom***255***255*** *** *** ***1***0***" +// into a json object "{ type: 'texte', name: 'bf_titre', label: 'Nom' .... }" +export function parseWikiTextIntoJsonData(text) { + const yesWikiMapping = getYesWikiMapping() + const result = [] + text = text.trim() + const textFields = text.split('\n') + for (let i = 0; i < textFields.length; i++) { + const textField = textFields[i] + const fieldValues = textField.split('***') + const fieldObject = {} + if (fieldValues.length > 1) { + const wikiType = fieldValues[0] + let fieldType = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].type : wikiType + // check that the fieldType really exists in our form builder + if (!(fieldType in yesWikiMapping)) fieldType = 'custom' + + const mapping = yesWikiMapping[fieldType] + + fieldObject.type = fieldType + fieldObject.subtype = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].subtype : '' + fieldObject.subtype2 = wikiType in yesWikiTypes ? yesWikiTypes[wikiType].subtype2 : '' + const start = fieldType == 'custom' ? 0 : 1 + for (let j = start; j < fieldValues.length; j++) { + let value = fieldValues[j] + const field = mapping && j in mapping ? mapping[j] : j + if (field == 'required') value = value == '1' + if (field) { + if (field == 'read' || field == 'write' || field == 'comment') { + fieldObject[field] = (value.trim() === '') + ? ( + field == 'comment' + ? [' + '] + : [' * '] + ) + : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) + } else if (field == 'seeEmailAcls') { + fieldObject[field] = (value.trim() === '') + ? ' % ' // if not define in tempalte, choose owner and admins + : value.split(',').map((e) => ((['+', '*', '%'].includes(e.trim())) ? ` ${e.trim()} ` : e)) + } else { + fieldObject[field] = value + } + } + } + if (!fieldObject.label) { + fieldObject.label = wikiType + for (let k = 0; k < fields.length; k++) if (fields[k].name == wikiType) fieldObject.label = fields[k].label + } + result.push(fieldObject) + } + } + if (wiki.isDebugEnabled) { + console.log('parse result', result) + } + return result +} + +// transform a json object like "{ type: 'texte', name: 'bf_titre', label: 'Nom' .... }" +// into wiki text like "texte***bf_titre***Nom***255***255*** *** *** ***1***0***" +export function formatJsonDataIntoWikiText(formData) { + if (formData.length == 0) return null + let wikiText = '' + const yesWikiMapping = getYesWikiMapping() + + for (let i = 0; i < formData.length; i++) { + const wikiProps = {} + const formElement = formData[i] + const mapping = yesWikiMapping[formElement.type] + + for (const type in yesWikiTypes) { + if ( + formElement.type == yesWikiTypes[type].type + && (!formElement.subtype + || !yesWikiTypes[type].subtype + || formElement.subtype == yesWikiTypes[type].subtype) + && (!formElement.subtype2 + || formElement.subtype2 == yesWikiTypes[type].subtype2) + ) { + wikiProps[0] = type + break + } + } + // for non mapped fields, we just keep the form type + if (!wikiProps[0]) wikiProps[0] = formElement.type + + // fix for url field which can be build with textField or urlField + if (wikiProps[0]) wikiProps[0] = wikiProps[0].replace('_bis', '') + + for (const key in mapping) { + const property = mapping[key] + if (property != 'type') { + let value = formElement[property] + if (['required', 'access'].indexOf(property) > -1) value = value ? '1' : '0' + if (property == 'label') { + wikiProps[key] = removeBR(value).replace(/\n$/gm, '') + } else { + wikiProps[key] = value + } + } + } + + const maxProp = Math.max.apply(Math, Object.keys(wikiProps)) + for (let j = 0; j <= maxProp; j++) { + wikiText += wikiProps[j] || ' ' + wikiText += '***' + } + wikiText += '\n' + } + return wikiText +} \ No newline at end of file From 078a240f29cac237e917c9c5df996962daf8386a Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 017/152] feat(form-builder): show advanced params --- .eslintrc.js | 47 ++++++++++--------- tools/bazar/lang/bazarjs_en.inc.php | 3 +- tools/bazar/lang/bazarjs_fr.inc.php | 4 +- .../advanced-attributes-display.js | 18 +++++++ .../form-edit-template/fields/map.js | 4 +- .../form-edit-template/fields/text.js | 1 + .../form-edit-template/form-edit-template.js | 9 ++-- .../styles/form-edit-template.css | 15 +++++- 8 files changed, 67 insertions(+), 34 deletions(-) create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/advanced-attributes-display.js diff --git a/.eslintrc.js b/.eslintrc.js index e6aab1bf1..ed0b866fd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,6 +24,7 @@ module.exports = { 'class-methods-use-this': 'off', 'import/no-unresolved': 'off', 'import/extensions': ['error', 'always'], + 'import/prefer-default-export': ['off'], eqeqeq: ['error', 'smart'], 'comma-dangle': ['error', 'never'], 'object-curly-newline': ['error', { multiline: true }], @@ -32,28 +33,28 @@ module.exports = { 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }] }, ignorePatterns: [ - "vendor/", - "!/custom", - "!/javascripts", - "javascripts/vendor", - "!/styles", - "!/tools", - "/tools/*", - "!tools/autoupdate", - "!tools/aceditor", - "!tools/attach", - "!tools/bazar", - "!tools/contact", - "!tools/helloworld", - "!tools/lang", - "!tools/login", - "!tools/progressbar", - "!tools/rss", - "!tools/security", - "!tools/syndication", - "!tools/tableau", - "!tools/tags", - "!tools/templates", - "!tools/toc" + 'vendor/', + '!/custom', + '!/javascripts', + 'javascripts/vendor', + '!/styles', + '!/tools', + '/tools/*', + '!tools/autoupdate', + '!tools/aceditor', + '!tools/attach', + '!tools/bazar', + '!tools/contact', + '!tools/helloworld', + '!tools/lang', + '!tools/login', + '!tools/progressbar', + '!tools/rss', + '!tools/security', + '!tools/syndication', + '!tools/tableau', + '!tools/tags', + '!tools/templates', + '!tools/toc' ] } diff --git a/tools/bazar/lang/bazarjs_en.inc.php b/tools/bazar/lang/bazarjs_en.inc.php index d22ddde32..1f1502d16 100644 --- a/tools/bazar/lang/bazarjs_en.inc.php +++ b/tools/bazar/lang/bazarjs_en.inc.php @@ -38,6 +38,7 @@ 'BAZ_FORM_EDIT_MAP_AUTOCOMPLETE_TOWN_PLACEHOLDER' => 'bf_ville', 'BAZ_FORM_EDIT_MAP_LATITUDE' => 'latitude fieldname', 'BAZ_FORM_EDIT_MAP_LONGITUDE' => 'longitude fieldname', + 'BAZ_FORM_ADVANCED_PARAMS' => 'See advanced params', 'BAZ_GEOLOC_NOT_FOUND' => 'Not found address, please move the point or give the coordinates', 'BAZ_MAP_ERROR' => 'An error occured: {msg}', 'BAZ_NOT_VALID_GEOLOC_FORMAT' => 'Bad GPS coordinates format (only nubers and one point for decimal)', @@ -47,8 +48,6 @@ 'BAZ_TOWN_NOT_FOUND' => 'No town found for search : {input}', 'GEOLOCATER_GROUP_GEOLOCATIZATION' => 'Fields to use for geolocalization', 'GEOLOCATER_GROUP_GEOLOCATIZATION_HINT' => 'Provide at least one field', - 'GEOLOCATER_SEE_ADVANCED_PARAMS' => '▼ See advanced params', - 'GEOLOCATER_HIDE_ADVANCED_PARAMS' => '▲ Hide advanced params', 'GEOLOCATER_NOT_FOUND' => 'Not foundable "{addr}" address', // libs/bazar.edit_lists.js diff --git a/tools/bazar/lang/bazarjs_fr.inc.php b/tools/bazar/lang/bazarjs_fr.inc.php index 758371610..5667d260e 100644 --- a/tools/bazar/lang/bazarjs_fr.inc.php +++ b/tools/bazar/lang/bazarjs_fr.inc.php @@ -39,6 +39,7 @@ 'BAZ_FORM_EDIT_MAP_LATITUDE' => 'Nom champ latitude', 'BAZ_FORM_EDIT_MAP_LONGITUDE' => 'Nom champ longitude', 'BAZ_GEOLOC_NOT_FOUND' => 'Adresse non trouvée, veuillez déplacer le point ou indiquer les coordonnées GPS', + 'BAZ_FORM_ADVANCED_PARAMS' => 'Voir les paramètres avancés', 'BAZ_MAP_ERROR' => 'Une erreur est survenue: {msg}', 'BAZ_NOT_VALID_GEOLOC_FORMAT' => 'Format de coordonnées GPS non valide (que des chiffres et un point . pour les décimales)', 'BAZ_POSTAL_CODE_HINT' => 'Veuillez entrer 5 chiffres pour voir les villes associées au code postal', @@ -47,8 +48,7 @@ 'BAZ_TOWN_NOT_FOUND' => 'Pas de ville trouvée pour la recherche : {input}', 'GEOLOCATER_GROUP_GEOLOCATIZATION' => 'Champs à utiliser pour la géolocalisation', 'GEOLOCATER_GROUP_GEOLOCATIZATION_HINT' => 'Renseignez au moins un champ', - 'GEOLOCATER_SEE_ADVANCED_PARAMS' => '▼ Voir les paramètres avancés', - 'GEOLOCATER_HIDE_ADVANCED_PARAMS' => '▲ Masquer les paramètres avancés', + 'GEOLOCATER_NOT_FOUND' => 'Adresse "{addr}" introuvable', // libs/bazar.edit_lists.js diff --git a/tools/bazar/presentation/javascripts/form-edit-template/advanced-attributes-display.js b/tools/bazar/presentation/javascripts/form-edit-template/advanced-attributes-display.js new file mode 100644 index 000000000..1478d7a12 --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/advanced-attributes-display.js @@ -0,0 +1,18 @@ +export function addAdvancedAttributesSection() { + $('#form-builder-container .form-field:not(.advanced-attributes-initialized)').each(function() { + const $field = $(this) + $field.addClass('advanced-attributes-initialized') + const fieldName = $field.attr('type') + const advancedAttributes = window.formBuilderFields[fieldName].advancedAttributes || [] + if (advancedAttributes.length > 0) { + advancedAttributes.forEach((attr) => { + $field.find(`.${attr}-wrap`).addClass('advanced') + }) + const $button = $(``) + $button.on('click', () => $field.toggleClass('show-advanced-attributes')) + $field.find('.form-elements').append($button) + } + }) +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js index bf3988445..d5f65c781 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js @@ -169,7 +169,7 @@ export default {
- +
`) @@ -177,11 +177,9 @@ export default { $advancedParams.find('button').on('click', function(event) { if ($(this).hasClass('opened')) { $(this).removeClass('opened') - $(this).html(_t('GEOLOCATER_SEE_ADVANCED_PARAMS')) toggleStates('hide') } else { $(this).addClass('opened') - $(this).html(_t('GEOLOCATER_HIDE_ADVANCED_PARAMS')) toggleStates('show') } event.preventDefault() diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/text.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/text.js index d4f2ac6c0..13fd3c851 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/text.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/text.js @@ -33,6 +33,7 @@ export default { placeholder: `${_t('BAZ_FORM_EDIT_ADVANCED_MODE')} Ex: [0-9]+ ou [A-Za-z]{3}, ...` } }, + advancedAttributes: ['read', 'write', 'semantic', 'pattern'], // disabledAttributes: [], renderInput(fieldData) { let string = `') diff --git a/tools/bazar/presentation/styles/form-edit-template.css b/tools/bazar/presentation/styles/form-edit-template.css index b15f84fdb..019fa140b 100644 --- a/tools/bazar/presentation/styles/form-edit-template.css +++ b/tools/bazar/presentation/styles/form-edit-template.css @@ -48,7 +48,7 @@ border: none; box-shadow: none; } -#form-builder-container .form-wrap.form-builder .frmb .form-elements .input-wrap>input[type='checkbox'] { +#form-builder-container .form-wrap.form-builder .frmb .form-elements .input-wrap>input[type='checkbox'] { margin: 0; } /* Remove checkbox style inside form-builder*/ @@ -59,6 +59,19 @@ pointer-events: initial; } +/* Advanced Attributes */ +#form-builder-container .form-field:not(.show-advanced-attributes) .form-group.advanced { + display: none !important; +} +#form-builder-container .form-field .form-group.advanced { + order: 200 !important; +} +#form-builder-container .show-advanced-attributes-btn { + order: 199; + margin: .5rem 0 1rem 0; +} + +/* Attributes */ #form-builder-container .required-wrap { display: flex; align-items: center From 3fcac7e4c22f0eb2a3d29b927a880bb71704719a Mon Sep 17 00:00:00 2001 From: Marseault Laurent Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 018/152] Update textarea.js, advanced attributes --- .../javascripts/form-edit-template/fields/textarea.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js index b4522bdc3..3283bb82b 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/textarea.js @@ -18,7 +18,7 @@ export default { } }, hint: { label: _t('BAZ_FORM_EDIT_HELP'), value: '' }, - size: { label: _t('BAZ_FORM_EDIT_TEXTAREA_SIZE_LABEL'), value: '' }, + // size: { label: _t('BAZ_FORM_EDIT_TEXTAREA_SIZE_LABEL'), value: '' }, rows: { label: _t('BAZ_FORM_EDIT_TEXTAREA_ROWS_LABEL'), type: 'number', @@ -28,7 +28,9 @@ export default { write: writeconf, semantic: semanticConf }, + advancedAttributes: ['read', 'write', 'semantic', 'pattern', 'syntax', 'rows'], // disabledAttributes: [], attributesMapping: { ...defaultMapping, ...{ 4: 'rows', 7: 'syntax' } }, // renderInput(fieldData) {}, } + From a195001451264d6ffcfb4c4bcb06046515f02ac9 Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 019/152] Update champs_mail.js advancedAttributes --- .../javascripts/form-edit-template/fields/champs_mail.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js index b84b48de9..14bad7fb7 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/champs_mail.js @@ -28,6 +28,7 @@ export default { write: writeconf, semantic: semanticConf }, + advancedAttributes: ['read', 'write', 'semantic', 'pattern', 'defaultIdentifier', 'name', 'seeEmailAcls', 'readWhenForm'], // disabledAttributes: [], attributesMapping: { ...defaultMapping, From 9090b792bceeecdbb2c1136a36790dab3c1c2a3e Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 020/152] Update url.js advancedAttributes --- .../presentation/javascripts/form-edit-template/fields/url.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/url.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/url.js index d5ccd7da7..6ad7a37ee 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/url.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/url.js @@ -12,6 +12,7 @@ export default { write: writeconf, semantic: semanticConf }, + advancedAttributes: ['read', 'write', 'semantic'], // disabledAttributes: [], renderInput(fieldData) { return { field: `` } From d29e67606a633ec2c353772e018e0d7afbb46951 Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 021/152] Update utilisateur_wikini.js advancedAttributes --- .../javascripts/form-edit-template/fields/utilisateur_wikini.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/utilisateur_wikini.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/utilisateur_wikini.js index 72c2b5501..efba77bcb 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/utilisateur_wikini.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/utilisateur_wikini.js @@ -28,6 +28,7 @@ export default { description: _t('BAZ_FORM_EDIT_ADD_TO_GROUP_DESCRIPTION') } }, + advancedAttributes: ['autoupdate_email', 'auto_add_to_group'], // disabledAttributes: [], attributesMapping: { ...defaultMapping, From 92f8f698379dcbf7052f16739f798e0c4d17c0d2 Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 022/152] Update image.js advancedAttributes --- .../presentation/javascripts/form-edit-template/fields/image.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js index 1669507fa..13dc3881e 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/image.js @@ -23,6 +23,7 @@ export default { write: writeconf, semantic: semanticConf }, + advancedAttributes: ['read', 'write', 'semantic', 'thumb_height','thumb_width','resize_height','resize_width'], // disabledAttributes: [], attributesMapping: { ...defaultMapping, From 255cc46d667266d6088e9cbf231a37fb412dbfb6 Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 023/152] Update checkbox-group.js advancedAttributes --- .../javascripts/form-edit-template/fields/checkbox-group.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/checkbox-group.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/checkbox-group.js index 5e4cf3a09..dcb34c29d 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/checkbox-group.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/checkbox-group.js @@ -25,6 +25,7 @@ export default { } } }, + advancedAttributes: ['read', 'write', 'semantic', 'queries', 'options'], // disabledAttributes: [], attributesMapping: { ...listsMapping, ...{ 7: 'fillingMode' } }, // renderInput(fieldData) {}, From b1b65f0d128da50fb7b0560b2766ae832abf4bf2 Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 024/152] Update date.js advancedAttributes --- .../presentation/javascripts/form-edit-template/fields/date.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js index e17320863..937f8faee 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/date.js @@ -18,6 +18,7 @@ export default { write: writeconf, semantic: semanticConf }, + advancedAttributes: ['read', 'write', 'semantic', 'today_button'], // disabledAttributes: [], attributesMapping: { ...defaultMapping, ...{ 5: 'today_button' } }, // renderInput(fieldData) {}, From ea8581cfcfe0264294f1786095948191ec67b2da Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 025/152] Update file.js advancedAttributes --- .../presentation/javascripts/form-edit-template/fields/file.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/file.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/file.js index c8d5a434a..1248fdec1 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/file.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/file.js @@ -13,6 +13,7 @@ export default { write: writeconf, semantic: semanticConf }, + advancedAttributes: ['read', 'write', 'semantic', 'maxsize'], // disabledAttributes: [], attributesMapping: { ...defaultMapping, ...{ 3: 'maxsize', 6: 'readlabel' } }, // renderInput(fieldData) {}, From 958bc71fef6d05e86141d181634af3459c7601dc Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 026/152] Update listefichesliees.js advancedAttributes --- .../javascripts/form-edit-template/fields/listefichesliees.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js index a090fe83e..0f1c9b1e8 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js @@ -36,6 +36,7 @@ export default { write: writeconf, semantic: semanticConf }, + advancedAttributes: ['read', 'write', 'semantic', 'template', 'type_link', 'param','query'], // disabledAttributes: [], attributesMapping: { 0: 'type', From ae1d885ae8b15f9f78a7505d5ae32e8d29670b3b Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 027/152] Update map.js advancedAttributes --- .../presentation/javascripts/form-edit-template/fields/map.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js index d5f65c781..67e9812a7 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js @@ -23,6 +23,7 @@ export default { options: { 0: _t('NO'), 1: _t('YES') } } }, + advancedAttributes: ['read', 'write', 'semantic', 'geolocate', 'autocomplete_other', 'autocomplete_street1','autocomplete_street2'], // disabledAttributes: [], attributesMapping: { 0: 'type', From e5902dfe0fd64313b1436e3b886e395febe2b459 Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 028/152] Update radio-group.js advancedAttributes --- .../javascripts/form-edit-template/fields/radio-group.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/radio-group.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/radio-group.js index f7631609c..27e2e6e89 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/radio-group.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/radio-group.js @@ -24,6 +24,7 @@ export default { } } }, + advancedAttributes: ['read', 'write', 'semantic', 'queries', 'fillingMode','options'], // disabledAttributes: [], attributesMapping: { ...listsMapping, ...{ 7: 'fillingMode' } }, // renderInput(fieldData) {}, From d456656635b999c70f9487f47e374a88c57d7729 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 029/152] feat(form-builder): remove capitalization --- tools/bazar/presentation/styles/form-edit-template.css | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/styles/form-edit-template.css b/tools/bazar/presentation/styles/form-edit-template.css index 019fa140b..d712ec40d 100644 --- a/tools/bazar/presentation/styles/form-edit-template.css +++ b/tools/bazar/presentation/styles/form-edit-template.css @@ -22,6 +22,7 @@ float: none; padding: 0; overflow: revert; + text-transform: none; } #form-builder-container .form-wrap.form-builder .stage-wrap { width: 74%; From e672e8d1a7bd96bdd5a9b373dbebcf34b9630f94 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 030/152] refactor(form-builder): list-form-id attribute --- .../fields/commons/attributes.js | 21 +++++---------- .../form-edit-template/form-edit-template.js | 27 ++----------------- .../list-form-id-attribute.js | 24 +++++++++++++++++ 3 files changed, 32 insertions(+), 40 deletions(-) create mode 100644 tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/attributes.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/attributes.js index e7ef3c815..18c12b416 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/attributes.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/commons/attributes.js @@ -1,20 +1,19 @@ -// groupsList and formAndListIds variables are created in forms_form.twig - -const $formBuilderTextInput = $('#form-builder-text') +// groupsList and formAndListIds are defined in forms_form.twig // When user add manuall via wikiCode a list or a formId that does not exist, keep the value // so it can be added in the select option list -const listAndFormUserValues = {} -$formBuilderTextInput.val().trim().split('\n').forEach((textField) => { +const _listAndFormUserValues = {} +$('#form-builder-text').val().trim().split('\n').forEach((textField) => { const fieldValues = textField.split('***') if (fieldValues.length > 1) { const [field, value] = fieldValues if (['checkboxfiche', 'checkbox', 'liste', 'radio', 'listefiche', 'radiofiche'].includes(field) - && value && !(value in formAndListIds)) { - listAndFormUserValues[value] = value + && value && value != ' ' && !(value in formAndListIds.forms) && !(value in formAndListIds.lists)) { + _listAndFormUserValues[value] = value } } }) +export const listAndFormUserValues = _listAndFormUserValues // Some attributes configuration used in multiple fields export const visibilityOptions = { @@ -88,14 +87,6 @@ export const selectConf = { ...listAndFormUserValues } }, - listId: { - label: '', - options: { ...formAndListIds.lists, ...listAndFormUserValues } - }, - formId: { - label: '', - options: { ...formAndListIds.forms, ...listAndFormUserValues } - }, defaultValue: { label: _t('BAZ_FORM_EDIT_SELECT_DEFAULT'), value: '' diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js index d54ffeeee..b52699c21 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js @@ -28,6 +28,7 @@ import tabchange from './fields/tabchange.js' import { parseWikiTextIntoJsonData, formatJsonDataIntoWikiText } from './yeswiki-syntax-converter.js' import { copyMultipleSelectValues, mapFieldsConf } from './form-builder-helper.js' import { addAdvancedAttributesSection } from './advanced-attributes-display.js' +import { initLitsOrFormIdAttribute } from './list-form-id-attribute.js' import I18nOption from './i18n.js' const $formBuilderTextInput = $('#form-builder-text') @@ -136,6 +137,7 @@ function initializeFormbuilder() { $('.fld-name').each(function() { existingFieldsNames.push($(this).val()) }) addAdvancedAttributesSection() + initLitsOrFormIdAttribute() // Transform input[textarea] in real textarea $('input[type="textarea"]').replaceWith(function() { @@ -165,31 +167,6 @@ function initializeFormbuilder() { if (wikiText) $formBuilderTextInput.val(wikiText) } - // when selecting between data source lists or forms, we need to populate again the - // listOfFormId select with the proper set of options - $('.radio-group-field, .checkbox-group-field, .select-field') - .find('select[name=subtype2]:not(.initialized)') - .on('change', function() { - $(this).addClass('initialized') - const visibleSelect = $(this) - .closest('.form-field') - .find('select[name=listeOrFormId]') - const selectedValue = visibleSelect.val() - visibleSelect.empty() - const optionToAddToSelect = $(this) - .closest('.form-field') - .find(`select[name=${$(this).val()}Id] option`) - visibleSelect.append(new Option('', '', false)) - optionToAddToSelect.each(function() { - const optionKey = $(this).attr('value') - const optionLabel = $(this).text() - const isSelected = optionKey == selectedValue - const newOption = new Option(optionLabel, optionKey, false, isSelected) - visibleSelect.append(newOption) - }) - }) - .trigger('change') - $('.fld-name').each(function() { let name = $(this).val() const id = $(this).closest('.form-field').attr('id') diff --git a/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js b/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js new file mode 100644 index 000000000..579206efe --- /dev/null +++ b/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js @@ -0,0 +1,24 @@ + +import { listAndFormUserValues } from './fields/commons/attributes.js' + +// formAndListIds is defined in forms_form.twig + +export function initLitsOrFormIdAttribute() { + // when selecting between data source lists or forms, we need to populate again the + // listOfFormId select with the proper set of options + $('.radio-group-field, .checkbox-group-field, .select-field') + .find('select[name=subtype2]:not(.list-form-attr-initialized)') + .on('change', function() { + $(this).addClass('list-form-attr-initialized') + const $select = $(this).closest('.form-field').find('select[name=listeOrFormId]') + const currentValue = $select.val() + $select.empty() + $select.append(new Option('', '', false)) + const optionToAddToSelect = { ...formAndListIds[`${$(this).val()}s`], ...listAndFormUserValues } + Object.entries(optionToAddToSelect).forEach(([key, label]) => { + const newOption = new Option(label, key, false, key == currentValue) + $select.append(newOption) + }) + }) + .trigger('change') +} From 152b9e574a81ef995c25ebade8c8b45f3750d6ad Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 031/152] update rules --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index ed0b866fd..b92707670 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,6 +25,7 @@ module.exports = { 'import/no-unresolved': 'off', 'import/extensions': ['error', 'always'], 'import/prefer-default-export': ['off'], + 'no-use-before-define': ['off'], eqeqeq: ['error', 'smart'], 'comma-dangle': ['error', 'never'], 'object-curly-newline': ['error', { multiline: true }], From 522b25860e74c9d084832dd99ef723a4855f12af Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 032/152] feat(form-builder): buttons to create/edit list --- .../list-form-id-attribute.js | 68 +++++++++++++++---- .../styles/form-edit-template.css | 8 +++ 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js b/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js index 579206efe..714baf0b1 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js @@ -6,19 +6,57 @@ import { listAndFormUserValues } from './fields/commons/attributes.js' export function initLitsOrFormIdAttribute() { // when selecting between data source lists or forms, we need to populate again the // listOfFormId select with the proper set of options - $('.radio-group-field, .checkbox-group-field, .select-field') - .find('select[name=subtype2]:not(.list-form-attr-initialized)') - .on('change', function() { - $(this).addClass('list-form-attr-initialized') - const $select = $(this).closest('.form-field').find('select[name=listeOrFormId]') - const currentValue = $select.val() - $select.empty() - $select.append(new Option('', '', false)) - const optionToAddToSelect = { ...formAndListIds[`${$(this).val()}s`], ...listAndFormUserValues } - Object.entries(optionToAddToSelect).forEach(([key, label]) => { - const newOption = new Option(label, key, false, key == currentValue) - $select.append(newOption) - }) - }) - .trigger('change') + $('.listeOrFormId-wrap:not(.initialized)').each(function() { + const $attributeWrap = $(this) + const $select = $attributeWrap.find('select[name=listeOrFormId]') + const $sourceSelect = $attributeWrap.siblings('.subtype2-wrap').find('select') + + $attributeWrap.addClass('initialized') + + addCreateEditListButton($select) + + $sourceSelect.on('change', () => { + $attributeWrap.attr('data-source', $sourceSelect.val()) + updateOptionsList($select, $sourceSelect) + toggleEditListButtonVisbility($attributeWrap, $select) + }).trigger('change') + + $select.on('change', () => { + toggleEditListButtonVisbility($attributeWrap, $select) + }).trigger('change') + }) +} + +function updateOptionsList($select, $sourceSelect) { + const currentValue = $select.val() + $select.empty() + $select.append(new Option('', '', false)) + const optionToAddToSelect = { ...formAndListIds[`${$sourceSelect.val()}s`], ...listAndFormUserValues } + + Object.entries(optionToAddToSelect).forEach(([key, label]) => { + const newOption = new Option(label, key, false, key == currentValue) + $select.append(newOption) + }) +} + +function toggleEditListButtonVisbility($attributeWrap, $select) { + const $btn = $attributeWrap.find('.edit-list-btn') + $select.val() ? $btn.show() : $btn.hide() +} + +function addCreateEditListButton($select) { + const $editListButton = $(``) + $editListButton.on('click', () => { + console.log("Edit list", $select.val()) + }) + + const $createListButton = $(``) + $createListButton.on('click', () => { + console.log("Create new list") + }) + $select.closest('.input-wrap').append($editListButton).append($createListButton) } diff --git a/tools/bazar/presentation/styles/form-edit-template.css b/tools/bazar/presentation/styles/form-edit-template.css index d712ec40d..d7ced4eab 100644 --- a/tools/bazar/presentation/styles/form-edit-template.css +++ b/tools/bazar/presentation/styles/form-edit-template.css @@ -88,6 +88,14 @@ .form-field { overflow: hidden; } +#form-builder-container .listeOrFormId-wrap .input-wrap { + display: flex; +} +#form-builder-container .listeOrFormId-wrap:not([data-source=list]) .edit-list-btn, +#form-builder-container .listeOrFormId-wrap:not([data-source=list]) .add-list-btn { + display: none !important; +} + /* Order of the field attributes */ .form-wrap.form-builder .frmb .form-elements { display: flex; flex-direction: column; } .form-wrap.form-builder .frmb .form-field .form-group { order: 100; } From a34fa0df89c7a2a9fe2f056532757a6eaf7544cf Mon Sep 17 00:00:00 2001 From: gatienbataille Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 033/152] Update select.js advancedAttributes --- .../presentation/javascripts/form-edit-template/fields/select.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/select.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/select.js index 4e52c54fc..c6675e181 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/select.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/select.js @@ -17,6 +17,7 @@ export default { } } }, + advancedAttributes: ['read', 'write', 'semantic', 'queries'], // disabledAttributes: [], attributesMapping: listsMapping, // renderInput(fieldData) {}, From 6f80fcee92a58cbc96cd2557c325c348f3638559 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 034/152] refactor(form-builder): use formbuilder onAddField callback --- .../advanced-attributes-display.js | 18 --------- .../list-form-id-attribute.js | 2 +- .../form-edit-template/form-builder-helper.js | 33 ++++++++++++++++ .../form-edit-template/form-edit-template.js | 39 +++++++++---------- 4 files changed, 52 insertions(+), 40 deletions(-) delete mode 100644 tools/bazar/presentation/javascripts/form-edit-template/advanced-attributes-display.js rename tools/bazar/presentation/javascripts/form-edit-template/{ => attributes}/list-form-id-attribute.js (96%) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/advanced-attributes-display.js b/tools/bazar/presentation/javascripts/form-edit-template/advanced-attributes-display.js deleted file mode 100644 index 1478d7a12..000000000 --- a/tools/bazar/presentation/javascripts/form-edit-template/advanced-attributes-display.js +++ /dev/null @@ -1,18 +0,0 @@ -export function addAdvancedAttributesSection() { - $('#form-builder-container .form-field:not(.advanced-attributes-initialized)').each(function() { - const $field = $(this) - $field.addClass('advanced-attributes-initialized') - const fieldName = $field.attr('type') - const advancedAttributes = window.formBuilderFields[fieldName].advancedAttributes || [] - if (advancedAttributes.length > 0) { - advancedAttributes.forEach((attr) => { - $field.find(`.${attr}-wrap`).addClass('advanced') - }) - const $button = $(``) - $button.on('click', () => $field.toggleClass('show-advanced-attributes')) - $field.find('.form-elements').append($button) - } - }) -} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js b/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js similarity index 96% rename from tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js rename to tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js index 714baf0b1..0e457baa0 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/list-form-id-attribute.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js @@ -1,5 +1,5 @@ -import { listAndFormUserValues } from './fields/commons/attributes.js' +import { listAndFormUserValues } from '../fields/commons/attributes.js' // formAndListIds is defined in forms_form.twig diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js b/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js index cb022f56e..5d7d7e2c2 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js @@ -23,4 +23,37 @@ export function copyMultipleSelectValues(currentField) { currentSelect.val($(this).val()) }) } +} + +export function adjustDefaultAcls(field) { + if (!field.hasOwnProperty('read')) { + field.read = [' * ']// everyone by default + } + if (!field.hasOwnProperty('write')) { + field.write = (field.type === 'champs_mail') + ? [' % '] // owner and @admins by default for e-mail + : [' * '] // everyone by default + } + if (field.type === 'acls' && !field.hasOwnProperty('comment')) { + field.comment = ['comments-closed'] // comments-closed by default + } + if (field.type === 'champs_mail' && !('seeEmailAcls' in field)) { + field.seeEmailAcls = [' % '] // owner and @admins by default + } +} + +export function addAdvancedAttributesSection($field) { + if (!$field.attr('type')) return + + const advancedAttributes = window.formBuilderFields[$field.attr('type')].advancedAttributes || [] + if (advancedAttributes.length > 0) { + advancedAttributes.forEach((attr) => { + $field.find(`.form-elements .${attr}-wrap`).addClass('advanced') + }) + const $button = $(``) + $button.on('click', () => $field.toggleClass('show-advanced-attributes')) + $field.find('.form-elements').append($button) + } } \ No newline at end of file diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js index b52699c21..5242cab10 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js @@ -26,13 +26,12 @@ import tabs from './fields/tabs.js' import tabchange from './fields/tabchange.js' import { parseWikiTextIntoJsonData, formatJsonDataIntoWikiText } from './yeswiki-syntax-converter.js' -import { copyMultipleSelectValues, mapFieldsConf } from './form-builder-helper.js' -import { addAdvancedAttributesSection } from './advanced-attributes-display.js' -import { initLitsOrFormIdAttribute } from './list-form-id-attribute.js' +import { copyMultipleSelectValues, mapFieldsConf, addAdvancedAttributesSection, adjustDefaultAcls } from './form-builder-helper.js' +import { initLitsOrFormIdAttribute } from './attributes/list-form-id-attribute.js' import I18nOption from './i18n.js' const $formBuilderTextInput = $('#form-builder-text') -let formBuilder +window.formBuilder = undefined // Use window to make it available outside of module, so extension can adds their own fields window.formBuilderFields = { @@ -92,20 +91,21 @@ function initializeFormbuilder() { 'multiple' ], onAddField(fieldId, field) { - if (!field.hasOwnProperty('read')) { - field.read = [' * ']// everyone by default - } - if (!field.hasOwnProperty('write')) { - field.write = (field.type === 'champs_mail') - ? [' % '] // owner and @admins by default for e-mail - : [' * '] // everyone by default - } - if (field.type === 'acls' && !field.hasOwnProperty('comment')) { - field.comment = ['comments-closed'] // comments-closed by default - } - if (field.type === 'champs_mail' && !('seeEmailAcls' in field)) { - field.seeEmailAcls = [' % '] // owner and @admins by default - } + adjustDefaultAcls(field) + + // strange bug with jQuery Formbuilder, the fieldId given is not the last field, but + // the one just before... so incrementing the id manually + // transform frmb-XXXX-fld-6 into frmb-XXXX-fld-7 + fieldId = fieldId.replace(/(.*)-fld-(\d+)$/gim, (string ,formId, fieldId) => { + return `${formId}-fld-${parseInt(fieldId, 10) + 1}` + }) + + // Timeout to wait the field ot be rendered + setTimeout(() => { + const $field = $(`#${fieldId}`) + addAdvancedAttributesSection($field) + initLitsOrFormIdAttribute($field) + }, 0) } }) @@ -136,9 +136,6 @@ function initializeFormbuilder() { existingFieldsNames = [] $('.fld-name').each(function() { existingFieldsNames.push($(this).val()) }) - addAdvancedAttributesSection() - initLitsOrFormIdAttribute() - // Transform input[textarea] in real textarea $('input[type="textarea"]').replaceWith(function() { const domTextarea = document.createElement('textarea') From 0bdf6afc5dc71bb0a9175461fd3a97e61466c218 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 035/152] refactor(form-builder): extract some code --- .../attributes/list-form-id-attribute.js | 2 +- .../form-edit-template/form-builder-helper.js | 14 ++++- .../form-edit-template/form-edit-template.js | 62 +++++++++---------- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js b/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js index 0e457baa0..d6388caaf 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js @@ -3,7 +3,7 @@ import { listAndFormUserValues } from '../fields/commons/attributes.js' // formAndListIds is defined in forms_form.twig -export function initLitsOrFormIdAttribute() { +export function initListOrFormIdAttribute() { // when selecting between data source lists or forms, we need to populate again the // listOfFormId select with the proper set of options $('.listeOrFormId-wrap:not(.initialized)').each(function() { diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js b/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js index 5d7d7e2c2..4a3323c52 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-builder-helper.js @@ -56,4 +56,16 @@ export function addAdvancedAttributesSection($field) { $button.on('click', () => $field.toggleClass('show-advanced-attributes')) $field.find('.form-elements').append($button) } -} \ No newline at end of file +} + +export function adjustJqueryBuilderUI($field) { + // Change names + $field.find('.form-group.name-wrap label').text(_t('BAZ_FORM_EDIT_UNIQUE_ID')) + $field.find('.form-group.label-wrap label').text(_t('BAZ_FORM_EDIT_NAME')) + // Changes icons and icons helpers + $field.find('a[type=remove].formbuilder-icon-cancel') + .removeClass('formbuilder-icon-cancel').addClass('btn-icon') + .html('') + $field.find('a[type=copy].formbuilder-icon-copy').attr('title', _t('DUPLICATE')) + $field.find('a[type=edit].formbuilder-icon-pencil').attr('title', _t('BAZ_FORM_EDIT_HIDE')) +} diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js index 5242cab10..cf3308dab 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js @@ -26,8 +26,14 @@ import tabs from './fields/tabs.js' import tabchange from './fields/tabchange.js' import { parseWikiTextIntoJsonData, formatJsonDataIntoWikiText } from './yeswiki-syntax-converter.js' -import { copyMultipleSelectValues, mapFieldsConf, addAdvancedAttributesSection, adjustDefaultAcls } from './form-builder-helper.js' -import { initLitsOrFormIdAttribute } from './attributes/list-form-id-attribute.js' +import { + copyMultipleSelectValues, + mapFieldsConf, + addAdvancedAttributesSection, + adjustDefaultAcls, + adjustJqueryBuilderUI +} from './form-builder-helper.js' +import { initListOrFormIdAttribute } from './attributes/list-form-id-attribute.js' import I18nOption from './i18n.js' const $formBuilderTextInput = $('#form-builder-text') @@ -104,15 +110,16 @@ function initializeFormbuilder() { setTimeout(() => { const $field = $(`#${fieldId}`) addAdvancedAttributesSection($field) - initLitsOrFormIdAttribute($field) - }, 0) - } - }) + initListOrFormIdAttribute($field) + adjustJqueryBuilderUI($field) - // disable bf_titre identifier - $('.fld-name').each(function() { - if ($(this).val() === 'bf_titre') { - $(this).attr('disabled', true) + // disable bf_titre identifier edition + $field.find('.fld-name').each(function() { + if ($(this).val() === 'bf_titre') { + $(this).attr('disabled', true) + } + }) + }, 0) } }) @@ -130,24 +137,10 @@ function initializeFormbuilder() { formBuilderInitialized = true } if ($formBuilderTextInput.is(':focus')) return - // Change names - $('.form-group.name-wrap label').text(_t('BAZ_FORM_EDIT_UNIQUE_ID')) - $('.form-group.label-wrap label').text(_t('BAZ_FORM_EDIT_NAME')) + existingFieldsNames = [] $('.fld-name').each(function() { existingFieldsNames.push($(this).val()) }) - // Transform input[textarea] in real textarea - $('input[type="textarea"]').replaceWith(function() { - const domTextarea = document.createElement('textarea') - domTextarea.id = this.id - domTextarea.name = this.name - domTextarea.value = this.value - domTextarea.classList = this.classList - domTextarea.title = this.title - domTextarea.rows = $(this).attr('rows') - return domTextarea - }) - // Slugiy field names $('.fld-name').each(function() { const newValue = $(this) @@ -202,6 +195,18 @@ function initializeFormbuilder() { existingFieldsIds = getFieldsIds() + // Transform input[textarea] in real textarea + $('input[type="textarea"]').replaceWith(function() { + const domTextarea = document.createElement('textarea') + domTextarea.id = this.id + domTextarea.name = this.name + domTextarea.value = this.value + domTextarea.classList = this.classList + domTextarea.title = this.title + domTextarea.rows = $(this).attr('rows') + return domTextarea + }) + $('.text-field select[name=subtype]:not(.initialized)').on('change', function() { $(this).addClass('initialized') const $parent = $(this).closest('.form-field') @@ -228,13 +233,6 @@ function initializeFormbuilder() { newVal = newVal.replace(/,+/g, ',') $(this).val(newVal) }) - - // Changes icons and icons helpers - $('a[type=remove].icon-cancel') - .removeClass('icon-cancel') - .html('') - $('a[type=copy].icon-copy').attr('title', _t('DUPLICATE')) - $('a[type=edit].icon-pencil').attr('title', _t('BAZ_FORM_EDIT_HIDE')) }, 300) $('#formbuilder-link').click(initializeBuilderFromTextInput) From d8777e811ca0073d4b169323db5b1a4049290cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 036/152] fix(test-extension): remove js call beause not existing file --- tools/bazar/templates/forms/forms_form.twig | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/bazar/templates/forms/forms_form.twig b/tools/bazar/templates/forms/forms_form.twig index 4753d35fd..5b6980f82 100644 --- a/tools/bazar/templates/forms/forms_form.twig +++ b/tools/bazar/templates/forms/forms_form.twig @@ -1,7 +1,6 @@ {{ include_javascript('javascripts/vendor/jquery-ui-sortable/jquery-ui.min.js') }} {{ include_javascript('javascripts/vendor/formBuilder/form-builder.min.js') }} {{ include_javascript('tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js', false, true) }} -{{ include_javascript('tools/bazar/presentation/javascripts/form-edit-template/test-extension.js') }} {{ include_css('tools/bazar/presentation/styles/form-edit-template.css') }} Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 037/152] fix(yeswiki-syntax-converter): retrieve real fields --- .../form-edit-template/yeswiki-syntax-converter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/yeswiki-syntax-converter.js b/tools/bazar/presentation/javascripts/form-edit-template/yeswiki-syntax-converter.js index edf347b3e..ab9636e5e 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/yeswiki-syntax-converter.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/yeswiki-syntax-converter.js @@ -86,7 +86,11 @@ export function parseWikiTextIntoJsonData(text) { } if (!fieldObject.label) { fieldObject.label = wikiType - for (let k = 0; k < fields.length; k++) if (fields[k].name == wikiType) fieldObject.label = fields[k].label + for (const key in window.formBuilderFields) { + if (window.formBuilderFields[key].name == wikiType){ + fieldObject.label = window.formBuilderFields[key].label + } + } } result.push(fieldObject) } From 9554abaa8865c2a788bc1104338a2408ddc0e65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 038/152] fix(initializeBuilderFromTextInput): catch error to prevent infinite loop --- .../javascripts/form-edit-template/form-edit-template.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js index cf3308dab..28ea237dc 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js @@ -250,7 +250,11 @@ function getFieldsIds() { function initializeBuilderFromTextInput() { const jsonData = parseWikiTextIntoJsonData($formBuilderTextInput.val()) - formBuilder.actions.setData(JSON.stringify(jsonData)) + try { + formBuilder.actions.setData(JSON.stringify(jsonData)) + } catch (error) { + console.error(error) + } } $('a[href="#formbuilder"]').on('click', (event) => { From acee8bafc9bbd24b84311e8ade256896a65420af Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:55:47 +0200 Subject: [PATCH 039/152] feat(form-builder): add/edit List within modals --- handlers/IframeHandler.php | 20 ++++---- javascripts/helpers/remote-modal.js | 49 +++++++++++++++++++ lang/yeswikijs_en.php | 6 +++ lang/yeswikijs_fr.php | 6 +++ templates/iframe_result.twig | 4 ++ tools/bazar/actions/BazarAction.php | 2 +- tools/bazar/controllers/ListController.php | 21 +++++++- .../attributes/list-form-id-attribute.js | 32 ++++++++++-- .../form-edit-template/form-edit-template.js | 2 +- tools/bazar/services/ListManager.php | 2 + 10 files changed, 125 insertions(+), 19 deletions(-) create mode 100644 javascripts/helpers/remote-modal.js create mode 100644 templates/iframe_result.twig diff --git a/handlers/IframeHandler.php b/handlers/IframeHandler.php index c1dc77704..6f36c2736 100644 --- a/handlers/IframeHandler.php +++ b/handlers/IframeHandler.php @@ -127,16 +127,16 @@ private function renderWikiPage(): string $title = ($isUserFavorite) ? _t('FAVORITES_REMOVE') : _t('FAVORITES_ADD') ; // HEREDOC syntax $output .= << - -
-HTML ; + + + + HTML; } // on ajoute un bouton de partage, si &share=1 est présent dans l'url if (isset($_GET['share']) && $_GET['share'] == '1') { diff --git a/javascripts/helpers/remote-modal.js b/javascripts/helpers/remote-modal.js new file mode 100644 index 000000000..f817d94ce --- /dev/null +++ b/javascripts/helpers/remote-modal.js @@ -0,0 +1,49 @@ +export default function(title, url) { + const $modal = $(` + + `) + + $('body').append($modal) + + // auto resize iframe height + const $iframe = $modal.find('iframe') + const iframe = $iframe[0] + let timer = null + iframe.onload = function() { + // remove favorite button + $iframe.contents().find('.btn.favorites').remove() + // remove "back/cancel" button in list view + $iframe.contents().find('.btn-cancel-list').remove() + // auto adjust iframe height + timer = setInterval(() => { + if (!iframe.contentWindow) return + iframe.height = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; + }, 200) + } + + $modal.modal({ + show: true, + keyboard: false + }).on('hidden hidden.bs.modal', () => { + $modal.remove() + }) + + return { + close() { + $modal.modal('hide') + clearInterval(timer) + } + } +} diff --git a/lang/yeswikijs_en.php b/lang/yeswikijs_en.php index b74acaccc..a4d9decd8 100644 --- a/lang/yeswikijs_en.php +++ b/lang/yeswikijs_en.php @@ -100,6 +100,12 @@ 'FAVORITES_ADDED' => 'Favorite added', 'FAVORITES_REMOVED' => 'Favorite deleted', + // javascripts/list-form-id-attribute.js + 'LIST_CREATE_TITLE' => 'New List', + 'LIST_UPDATE_TITLE' => 'Update the List', + 'LIST_CREATED' => 'List successfully created', + 'LIST_UPDATED' => 'List successfully updated', + // javascripts/multidelete.js "MULTIDELETE_END" => "Deletions finished", "MULTIDELETE_ERROR" => "Item {itemId} has not been deleted! {error}", diff --git a/lang/yeswikijs_fr.php b/lang/yeswikijs_fr.php index 56ef61ebf..a8da3facd 100644 --- a/lang/yeswikijs_fr.php +++ b/lang/yeswikijs_fr.php @@ -101,6 +101,12 @@ 'FAVORITES_ADDED' => 'Favori ajouté', 'FAVORITES_REMOVED' => 'Favori supprimé', + // javascripts/list-form-id-attribute.js + 'LIST_CREATE_TITLE' => 'Nouvelle Liste', + 'LIST_UPDATE_TITLE' => 'Modifier la Liste', + 'LIST_CREATED' => 'La liste a bien été crée', + 'LIST_UPDATED' => 'La liste a été mise à jour avec succès', + // javascripts/multidelete.js "MULTIDELETE_END" => "Suppressions réalisées", "MULTIDELETE_ERROR" => "L'élément {itemId} n'a pas été supprimé ! {error}", diff --git a/templates/iframe_result.twig b/templates/iframe_result.twig new file mode 100644 index 000000000..0acd0f9a2 --- /dev/null +++ b/templates/iframe_result.twig @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/tools/bazar/actions/BazarAction.php b/tools/bazar/actions/BazarAction.php index 066183628..3d1dffbf3 100644 --- a/tools/bazar/actions/BazarAction.php +++ b/tools/bazar/actions/BazarAction.php @@ -62,7 +62,7 @@ public function formatArguments($arg) self::VARIABLE_ACTION => $_GET[self::VARIABLE_ACTION] ?? $arg[self::VARIABLE_ACTION] ?? null, self::VARIABLE_VOIR => $_GET[self::VARIABLE_VOIR] ?? $arg[self::VARIABLE_VOIR] ?? self::VOIR_DEFAUT, // afficher le menu de vues bazar ? - 'voirmenu' => $arg['voirmenu'] ?? $this->params->get('baz_menu'), + 'voirmenu' => $_GET['voirmenu'] ?? $arg['voirmenu'] ?? $this->params->get('baz_menu'), // Identifiant du formulaire (plusieures valeurs possibles, séparées par des virgules) 'idtypeannonce' => $this->formatArray($_REQUEST['id_typeannonce'] ?? $arg['id'] ?? $arg['idtypeannonce'] ?? (!empty($_GET['id']) ? strip_tags($_GET['id']) : null)), // Permet de rediriger vers une url après saisie de fiche diff --git a/tools/bazar/controllers/ListController.php b/tools/bazar/controllers/ListController.php index 2f13dd069..66899a149 100644 --- a/tools/bazar/controllers/ListController.php +++ b/tools/bazar/controllers/ListController.php @@ -56,8 +56,14 @@ public function create() ++$i; } } - - $this->listManager->create($_POST['titre_liste'], $values); + + $listeId = $this->listManager->create($_POST['titre_liste'], $values); + + if ($this->shouldPostMessageOnSubmit()) { + return $this->render('@core/iframe_result.twig', [ + "data" => ["msg" => "list_created", "id" => $listeId, "title" => $_POST['titre_liste']] + ]); + } $this->wiki->Redirect( $this->wiki->Href('', '', [BAZ_VARIABLE_VOIR => BAZ_VOIR_LISTES], false) @@ -67,6 +73,11 @@ public function create() return $this->render('@bazar/lists/list_form.twig'); } + private function shouldPostMessageOnSubmit() + { + return isset($_GET["onsubmit"]) && $_GET["onsubmit"] == "postmessage"; + } + public function update($id) { $list = $this->listManager->getOne($id); @@ -85,6 +96,12 @@ public function update($id) $this->listManager->update($id, $_POST['titre_liste'], $values); + if ($this->shouldPostMessageOnSubmit()) { + return $this->render('@core/iframe_result.twig', [ + "data" => ["msg" => "list_updated", "id" => $id, "title" => $_POST['titre_liste']] + ]); + } + $this->wiki->Redirect( $this->wiki->Href('', '', [BAZ_VARIABLE_VOIR => BAZ_VOIR_LISTES], false) ); diff --git a/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js b/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js index d6388caaf..eded7e636 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/attributes/list-form-id-attribute.js @@ -1,5 +1,5 @@ - import { listAndFormUserValues } from '../fields/commons/attributes.js' +import openRemoteModal from '../../../../../../javascripts/helpers/remote-modal.js' // formAndListIds is defined in forms_form.twig @@ -13,7 +13,7 @@ export function initListOrFormIdAttribute() { $attributeWrap.addClass('initialized') - addCreateEditListButton($select) + addCreateEditListButton($select, $sourceSelect) $sourceSelect.on('change', () => { $attributeWrap.attr('data-source', $sourceSelect.val()) @@ -44,19 +44,41 @@ function toggleEditListButtonVisbility($attributeWrap, $select) { $select.val() ? $btn.show() : $btn.hide() } -function addCreateEditListButton($select) { +function addCreateEditListButton($select, $sourceSelect) { const $editListButton = $(``) $editListButton.on('click', () => { - console.log("Edit list", $select.val()) + const url = wiki.url(`?BazaR/iframe&vue=listes&action=modif_liste&voirmenu=0&onsubmit=postmessage&idliste=${$select.val()}`) + const modal = openRemoteModal(_t('LIST_UPDATE_TITLE'), url) + window.onmessage = function(e) { + if (e.data.msg === 'list_updated') { + // update the options (list name might have changed) + formAndListIds.lists[e.data.id] = e.data.title + updateOptionsList($select, $sourceSelect) + toastMessage(_t('LIST_UPDATED'), 3000, 'alert alert-success') + modal.close() + } + } }) const $createListButton = $(``) $createListButton.on('click', () => { - console.log("Create new list") + const url = wiki.url('?BazaR/iframe&vue=listes&action=saisir_liste&voirmenu=0&onsubmit=postmessage') + const modal = openRemoteModal(_t('LIST_CREATE_TITLE'), url) + window.onmessage = function(e) { + if (e.data.msg === 'list_created') { + // update the options with the new List + formAndListIds.lists[e.data.id] = e.data.title + updateOptionsList($select, $sourceSelect) + // select the newly created List + $select.val(e.data.id) + toastMessage(_t('LIST_CREATED'), 3000, 'alert alert-success') + modal.close() + } + } }) $select.closest('.input-wrap').append($editListButton).append($createListButton) } diff --git a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js index 28ea237dc..6346f0b03 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/form-edit-template.js @@ -239,7 +239,7 @@ function initializeFormbuilder() { } document.addEventListener("DOMContentLoaded", function() { - initializeFormbuilder(); + initializeFormbuilder() }) function getFieldsIds() { diff --git a/tools/bazar/services/ListManager.php b/tools/bazar/services/ListManager.php index 3783023f4..00b260b7d 100644 --- a/tools/bazar/services/ListManager.php +++ b/tools/bazar/services/ListManager.php @@ -105,6 +105,8 @@ public function create($title, $values) ])); $this->tripleStore->create($id, TripleStore::TYPE_URI, self::TRIPLES_LIST_ID, '', ''); + + return $id; } public function update($id, $title, $values) From 19ed95827828bfe056bcf995c6f4c17b484c6416 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 11 Jun 2023 11:58:27 +0200 Subject: [PATCH 040/152] doc(form-builder): advancedAttributes --- .../presentation/javascripts/form-edit-template/fields/README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/README b/tools/bazar/presentation/javascripts/form-edit-template/fields/README index 2ed1f4fd9..d982070be 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/README +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/README @@ -12,6 +12,8 @@ export default { }, // Disable default attributes from jQuery formBuilder disabledAttributes: [], + // If you want some attributes to displayed in advance attributes section + advancedAttributes: [], // mapping for old yeswiki syntax (field***attribute***other_attribute) attributesMapping: {}, // How to render the input From 1f5f07dd7b1744db8c23e81a59088975991ffd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Mon, 12 Jun 2023 09:14:39 +0200 Subject: [PATCH 041/152] fix(BazarAction/ListController): more secure usage of $_GET --- tools/bazar/actions/BazarAction.php | 29 +++++++++++++++++++--- tools/bazar/controllers/ListController.php | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/tools/bazar/actions/BazarAction.php b/tools/bazar/actions/BazarAction.php index 3d1dffbf3..77fb3f50e 100644 --- a/tools/bazar/actions/BazarAction.php +++ b/tools/bazar/actions/BazarAction.php @@ -46,7 +46,9 @@ class BazarAction extends YesWikiAction public function formatArguments($arg) { - $redirecturl = $_GET['redirecturl'] ?? $arg['redirecturl'] ?? ''; + $redirecturl = $this->sanitizedGet('redirecturl',function() use($arg){ + return $arg['redirecturl'] ?? ''; + }); // YesWiki pages links, like "HomePage" or "HomePage/xml" if (!empty($redirecturl)){ $wikiLink = $this->wiki->extractLinkParts((substr($redirecturl,0,1) == '?') ? substr($redirecturl,1): $redirecturl); @@ -59,10 +61,16 @@ public function formatArguments($arg) } return([ - self::VARIABLE_ACTION => $_GET[self::VARIABLE_ACTION] ?? $arg[self::VARIABLE_ACTION] ?? null, - self::VARIABLE_VOIR => $_GET[self::VARIABLE_VOIR] ?? $arg[self::VARIABLE_VOIR] ?? self::VOIR_DEFAUT, + self::VARIABLE_ACTION => $this->sanitizedGet(self::VARIABLE_ACTION,function() use($arg){ + return $arg[self::VARIABLE_ACTION] ?? null; + }), + self::VARIABLE_VOIR => $this->sanitizedGet(self::VARIABLE_VOIR,function() use($arg){ + return $arg[self::VARIABLE_VOIR] ?? self::VOIR_DEFAUT; + }), // afficher le menu de vues bazar ? - 'voirmenu' => $_GET['voirmenu'] ?? $arg['voirmenu'] ?? $this->params->get('baz_menu'), + 'voirmenu' => $this->sanitizedGet('voirmenu',function() use($arg){ + return $arg['voirmenu'] ?? $this->params->get('baz_menu'); + }), // Identifiant du formulaire (plusieures valeurs possibles, séparées par des virgules) 'idtypeannonce' => $this->formatArray($_REQUEST['id_typeannonce'] ?? $arg['id'] ?? $arg['idtypeannonce'] ?? (!empty($_GET['id']) ? strip_tags($_GET['id']) : null)), // Permet de rediriger vers une url après saisie de fiche @@ -70,6 +78,19 @@ public function formatArguments($arg) ]); } + /** + * check if get is scalar then return it or result of callback + * @param string $key + * @param function $callback + * @return scalar + */ + protected function sanitizedGet(string $key,$callback) + { + return (isset($_GET[$key]) && is_scalar($_GET[$key])) + ? $_GET[$key] + : (is_callable($callback) ? $callback() : null); + } + public function run() { $listController = $this->getService(ListController::class); diff --git a/tools/bazar/controllers/ListController.php b/tools/bazar/controllers/ListController.php index 66899a149..b714e377b 100644 --- a/tools/bazar/controllers/ListController.php +++ b/tools/bazar/controllers/ListController.php @@ -75,7 +75,7 @@ public function create() private function shouldPostMessageOnSubmit() { - return isset($_GET["onsubmit"]) && $_GET["onsubmit"] == "postmessage"; + return isset($_GET["onsubmit"]) && $_GET["onsubmit"] === "postmessage"; } public function update($id) From 8d7e26d6c00516b6fc2a850451efa8c59ac25456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Mon, 12 Jun 2023 09:31:39 +0200 Subject: [PATCH 042/152] fix(forms_table): remove menu in popup when deleting a form --- includes/YesWiki.php | 2 +- tools/bazar/templates/forms/forms_table.twig | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/YesWiki.php b/includes/YesWiki.php index a5caa772f..f5d2ba394 100755 --- a/includes/YesWiki.php +++ b/includes/YesWiki.php @@ -400,7 +400,7 @@ public function Href($method = null, $tag = null, $params = null, $htmlspchars = if (is_array($params)) { $paramsArray = []; foreach ($params as $key => $value) { - if ($value) { + if (!empty($value) || in_array($value,[0,'0',''],true)) { $paramsArray[] = "$key=".urlencode($value); } }; diff --git a/tools/bazar/templates/forms/forms_table.twig b/tools/bazar/templates/forms/forms_table.twig index 6441b9feb..df02cdd56 100755 --- a/tools/bazar/templates/forms/forms_table.twig +++ b/tools/bazar/templates/forms/forms_table.twig @@ -96,10 +96,10 @@
{% if form.canDelete %} - + - + {% else %} From 6273aca6c72512eb9090987d468b8df71263ad29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Mon, 12 Jun 2023 09:40:02 +0200 Subject: [PATCH 043/152] fix(TabsField): do not open tabs if no tabs #fix 1081 --- tools/templates/controllers/TabsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/templates/controllers/TabsController.php b/tools/templates/controllers/TabsController.php index 2a39b0654..4d9d37af8 100644 --- a/tools/templates/controllers/TabsController.php +++ b/tools/templates/controllers/TabsController.php @@ -59,7 +59,7 @@ public function openTabs(string $mode,$data):string } else { return ''; } - return $this->render('@templates/tabs.twig',[ + return empty($titles) ? '' : $this->render('@templates/tabs.twig',[ 'titles' => $titles, 'selectedtab' => $selectedtab , 'slugs' => $this->tabsService->getSlugs($mode) From 16a3ceb96a43c96f62edded59ee21fc4e426fbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Fri, 28 Apr 2023 17:07:38 +0200 Subject: [PATCH 044/152] feat(tabdyn): import --- docs/actions/bazarliste.yaml | 272 +++++++ docs/actions/lang/actionsbuilder_en.inc.php | 6 + docs/actions/lang/actionsbuilder_fr.inc.php | 6 + docs/actions/lang/actionsbuilder_pt.inc.php | 6 + includes/controllers/ApiController.php | 35 + tools/bazar/actions/BazarCartoAction.php | 17 +- tools/bazar/actions/BazarListeAction.php | 18 +- tools/bazar/actions/BazarTableAction.php | 39 + .../javascripts/components/BazarTable.js | 767 ++++++++++++++++++ .../components/BazarTableEntrySelector.js | 27 + .../map-and-table.twig | 20 + .../index-dynamic-templates/table.twig | 140 ++++ 12 files changed, 1345 insertions(+), 8 deletions(-) create mode 100644 tools/bazar/actions/BazarTableAction.php create mode 100644 tools/bazar/presentation/javascripts/components/BazarTable.js create mode 100644 tools/bazar/presentation/javascripts/components/BazarTableEntrySelector.js create mode 100644 tools/bazar/templates/entries/index-dynamic-templates/map-and-table.twig create mode 100644 tools/bazar/templates/entries/index-dynamic-templates/table.twig diff --git a/docs/actions/bazarliste.yaml b/docs/actions/bazarliste.yaml index 4668a8533..bc442169c 100644 --- a/docs/actions/bazarliste.yaml +++ b/docs/actions/bazarliste.yaml @@ -30,6 +30,7 @@ actions: showOnlyFor: - bazarliste - bazarcarto + - bazarmapandtable showExceptFor: - bazarcarousel dynamic: @@ -37,7 +38,9 @@ actions: showOnlyFor: - bazarliste - bazarcarto + - bazarmapandtable - bazarcalendar + - bazartableau type: checkbox advanced: true pagination: @@ -46,6 +49,7 @@ actions: type: number showExceptFor: - bazarcarto + - bazarmapandtable - bazargogocarto - bazartimeline - bazarcarousel @@ -59,6 +63,7 @@ actions: only: lists # ce champ doit être de type checkboxListe, listeListe ... showOnlyFor: - bazarcarto + - bazarmapandtable - bazargogocarto - bazarliste - bazarcalendar @@ -68,6 +73,7 @@ actions: showif: colorfield showOnlyFor: - bazarcarto + - bazarmapandtable - bazargogocarto - bazarliste - bazarcalendar @@ -88,6 +94,7 @@ actions: only: lists # ce champ doit être de type checkboxListe, listeListe ... showOnlyFor: - bazarcarto + - bazarmapandtable - bazargogocarto - bazarliste - bazarcalendar @@ -97,6 +104,7 @@ actions: showif: iconfield showOnlyFor: - bazarcarto + - bazarmapandtable - bazargogocarto - bazarliste - bazarcalendar @@ -892,3 +900,267 @@ actions: advanced: true type: checkbox default: 'false' + + bazarmapandtable: + label: _t(AB_BAZAR_MAP_AND_TABLE_LABEL) + description: _t(AB_bazarcarto_description) + hint: _t(AB_bazarcarto_hint) + width: 35% + properties: + template: + value: map-and-table + provider: + label: _t(AB_bazarcarto_provider_label) + required: true + type: list + default: OpenStreetMap.Mapnik # TODO change to Stadia.OSMBright if community agrees? + options: + - OpenStreetMap.Mapnik + - OpenStreetMap.BlackAndWhite + - OpenStreetMap.DE + - OpenStreetMap.France + - OpenStreetMap.HOT + - OpenTopoMap + - Stadia.AlidadeSmooth + - Stadia.AlidadeSmoothDark + - Stadia.OSMBright + - Stamen.Toner + - Stamen.TonerBackground + - Stamen.TonerLite + - Stamen.Watercolor + - Stamen.Terrain + - Stamen.TerrainBackground + - Esri.WorldStreetMap + - Esri.DeLorme + - Esri.WorldTopoMap + - Esri.WorldImagery + - Esri.WorldTerrain + - Esri.WorldShadedRelief + - Esri.WorldPhysical + - Esri.OceanBasemap + - Esri.NatGeoWorldMap + - Esri.WorldGrayCanvas + - HERE.normalDay + - MtbMap + - CartoDB.Positron + - CartoDB.PositronNoLabels + - CartoDB.PositronOnlyLabels + - CartoDB.DarkMatter + - CartoDB.DarkMatterNoLabels + - CartoDB.DarkMatterOnlyLabels + - HikeBike.HikeBike + - HikeBike.HillShading + - BasemapAT.orthofoto + - NASAGIBS.ViirsEarthAtNight2012 + coordinates: + label: _t(AB_bazarcarto_coordinates_label) + type: geo + cluster: + label: _t(AB_bazarcarto_cluster_label) + type: checkbox + default: "false" + width: + advanced: true + label: _t(AB_bazarcarto_width_label) + hint: 500px, 100%... + type: text + default: 100% + height: + advanced: true + label: _t(AB_bazarcarto_height_label) + type: text + default: 300px + entrydisplay: + label: _t(AB_bazarcarto_entrydisplay_label) + type: list + default: sidebar + advanced: true + showif: dynamic + options: + direct: _t(AB_bazarcarto_entrydisplay_option_direct) + sidebar: _t(AB_bazarcarto_entrydisplay_option_sidebar) + modal: _t(AB_bazarcarto_entrydisplay_option_modal) + newtab: _t(AB_bazarcarto_entrydisplay_option_newtab) + popup: _t(AB_bazarcarto_entrydisplay_option_popup) + popuptemplate: + label: _t(AB_bazarcarto_popuptemplate_label) + type: list + value: '_map_popup_html.twig' + advanced: true + options: + '_map_popup_html.twig': _t(AB_bazarcarto_popuptemplate_entry_from_html) + '_map_popup_from_data.twig': _t(AB_bazarcarto_popuptemplate_entry_from_data) + 'custom': _t(AB_bazarcarto_popuptemplate_custom) + showif: + dynamic: true + entrydisplay: 'popup' + popupcustomtemplate: + label: _t(AB_bazarcarto_popupcustomtemplate_label) + type: text + value: 'custom_map_popup.twig' + hint: _t(AB_bazarcarto_popupcustomtemplate_hint) + advanced: true + showif: + dynamic: true + entrydisplay: 'popup' + popuptemplate: 'custom' + popupselectedfields: + type: form-field + label: _t(AB_bazarliste_popupselectedfields_label) + multiple: true + advanced: true + default: "" + showif: + dynamic: true + entrydisplay: 'popup' + popuptemplate: '_map_popup_html.twig|custom' + necessary_fields: + type: form-field + label: _t(AB_bazarliste_popupneededfields_label) + multiple: true + advanced: true + value: "bf_titre,imagebf_image" + showif: + dynamic: true + entrydisplay: 'popup' + popuptemplate: '_map_popup_from_data.twig|custom' + displayfields: + type: correspondance + showif: dynamic + advanced: true + subproperties: + markerhover: + type: form-field + label: _t(AB_bazarcarto_displayfields_markhover_label) + default: bf_titre + smallmarker: + advanced: true + label: _t(AB_bazarcarto_smallmarker_label) + type: checkbox + default: "0" + checkedvalue: "1" + uncheckedvalue: "0" + # spider: + # advanced: true + # label: _t(AB_bazarcarto_spider_label) + # hint: _t(AB_bazarcarto_spider_hint) + # type: checkbox + # default: "false" + # barregestion: + # advanced: true + # label: _t(AB_bazarcarto_barregestion_label) + # type: checkbox + # default: "false" + # navigation: + # advanced: true + # label: _t(AB_bazarcarto_navigation_label) + # type: checkbox + # default: "true" + zoommolette: + advanced: true + label: _t(AB_bazarcarto_zoommolette_label) + type: checkbox + default: "false" + columnfieldsids: + type: form-field + label: _t(AB_bazartableau_columnfieldsids_label) + hint: _t(AB_bazartableau_columnfieldsids_hint) + multiple: true + default: '' + columntitles: + type: text + label: _t(AB_bazartableau_columntitles_label) + hint: _t(AB_bazartableau_columntitles_hint) + default: '' + checkboxfieldsincolumns: + type: checkbox + label: _t(AB_bazartableau_checkboxfieldsincolumns_label) + default: "true" + checkedvalue: "true" + uncheckedvalue: "false" + displayimagesasthumbnails: + type: checkbox + label: _t(AB_bazartableau_displayimagesasthumbnails_label) + default: "false" + checkedvalue: "true" + uncheckedvalue: "false" + displayvaluesinsteadofkeys: + type: checkbox + label: _t(AB_bazartableau_displayvaluesinsteadofkeys_label) + default: "false" + checkedvalue: "true" + uncheckedvalue: "false" + sumfieldsids: + type: form-field + label: _t(AB_bazartableau_sumfieldsids_label) + hint: _t(AB_bazartableau_sumfieldsids_hint) + multiple: true + default: '' + displayadmincol: + type: list + label: _t(AB_bazartableau_displayadmincol_label) + hint: _t(AB_bazartableau_displayadmincol_hint) + default: '' + options: + '': _t(NO) + yes: _t(YES) + onlyadmins: _t(AB_bazartableau_displayadmincol_onlyadmins) + displaycreationdate: + type: list + label: _t(AB_bazartableau_displaycreationdate_label) + advanced: true + default: '' + options: + '': _t(NO) + yes: _t(YES) + onlyadmins: _t(AB_bazartableau_displayadmincol_onlyadmins) + displaylastchangedate: + type: list + label: _t(AB_bazartableau_displaylastchangedate_label) + advanced: true + default: '' + options: + '': _t(NO) + yes: _t(YES) + onlyadmins: _t(AB_bazartableau_displayadmincol_onlyadmins) + displayowner: + type: list + label: _t(AB_bazartableau_displayowner_label) + advanced: true + default: '' + options: + '': _t(NO) + yes: _t(YES) + onlyadmins: _t(AB_bazartableau_displayadmincol_onlyadmins) + defaultcolumnwidth: + label: _t(AB_bazartableau_defaultcolumnwidth_label) + hint: _t(AB_bazartableau_defaultcolumnwidth_hint) + type: text + advanced: true + default: '' + columnswidth: + label: _t(AB_bazartableau_columnswidth_label) + hint: _t(AB_bazartableau_columnswidth_hint) + type: columns-width + advanced: true + subproperties: + field: + label: _t(AB_bazartableau_columnswidth_field_label) + type: form-field + width: + label: _t(AB_bazartableau_columnswidth_width_label) + type: text + default: '' + exportallcolumns: + label: _t(AB_bazartableau_exportallcolumns_label) + advanced: true + type: checkbox + default: 'false' + tablewith: + label: _t(AB_BAZAR_MAP_AND_TABLE_TABLEWITH_LABEL) + type: list + default: '' + options: + '': _t(AB_BAZAR_MAP_AND_TABLE_TABLEWITH_ALL) + only-geolocation : _t(AB_BAZAR_MAP_AND_TABLE_TABLEWITH_ONLY_GEOLOC) + no-geolocation: _t(AB_BAZAR_MAP_AND_TABLE_TABLEWITH_NO_GEOLOC) diff --git a/docs/actions/lang/actionsbuilder_en.inc.php b/docs/actions/lang/actionsbuilder_en.inc.php index 78263c891..d89037b35 100644 --- a/docs/actions/lang/actionsbuilder_en.inc.php +++ b/docs/actions/lang/actionsbuilder_en.inc.php @@ -120,6 +120,12 @@ // "AB_bazarcard_style_vertical" => 'Vertical', // "AB_bazarcard_style_square" => 'Carré', // "AB_bazarcard_style_horizontal" => 'Horizontal', + // BazarTable + 'AB_BAZAR_MAP_AND_TABLE_LABEL' => 'Map and table', + 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_LABEL' => 'Table with all entries', + 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_ALL' => 'with or withour geolocalization', + 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_ONLY_GEOLOC' => 'only with geolocalization', + 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_NO_GEOLOC' => 'only without geolocalization', // // BazarTrombi // "AB_bazartrombi_label" => "Trombinoscope", // "AB_bazartrombi_description" => "Les fiches seront sous forme de Trombinoscope.", diff --git a/docs/actions/lang/actionsbuilder_fr.inc.php b/docs/actions/lang/actionsbuilder_fr.inc.php index d358ab0b5..737eaaf99 100644 --- a/docs/actions/lang/actionsbuilder_fr.inc.php +++ b/docs/actions/lang/actionsbuilder_fr.inc.php @@ -120,6 +120,12 @@ "AB_bazarcard_style_vertical" => 'Vertical', "AB_bazarcard_style_square" => 'Carré', "AB_bazarcard_style_horizontal" => 'Horizontal', + // BazarTable + 'AB_BAZAR_MAP_AND_TABLE_LABEL' => 'Carte et tableau', + 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_LABEL' => 'Tableau contenant toutes les fiches', + 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_ALL' => 'avec ou sans geolocalisation', + 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_ONLY_GEOLOC' => 'seulement avec geolocalisation', + 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_NO_GEOLOC' => 'seulement sans geolocalisation', // BazarTrombi "AB_bazartrombi_label" => "Trombinoscope", "AB_bazartrombi_description" => "Les fiches seront sous forme de Trombinoscope.", diff --git a/docs/actions/lang/actionsbuilder_pt.inc.php b/docs/actions/lang/actionsbuilder_pt.inc.php index 20427f803..2d84c1980 100644 --- a/docs/actions/lang/actionsbuilder_pt.inc.php +++ b/docs/actions/lang/actionsbuilder_pt.inc.php @@ -120,6 +120,12 @@ // "AB_bazarcard_style_vertical" => 'Vertical', // "AB_bazarcard_style_square" => 'Carré', // "AB_bazarcard_style_horizontal" => 'Horizontal', + // BazarTable + // 'AB_BAZAR_MAP_AND_TABLE_LABEL' => 'Carte et tableau', + // 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_LABEL' => 'Tableau contenant toutes les fiches', + // 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_ALL' => 'avec ou sans geolocalisation', + // 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_ONLY_GEOLOC' => 'seulement avec geolocalisation', + // 'AB_BAZAR_MAP_AND_TABLE_TABLEWITH_NO_GEOLOC' => 'seulement sans geolocalisation', // BazarTrombi // "AB_bazartrombi_label" => "Trombinoscope", // "AB_bazartrombi_description" => "Les fiches seront sous forme de Trombinoscope.", diff --git a/includes/controllers/ApiController.php b/includes/controllers/ApiController.php index 177272b45..ef8e8b53d 100644 --- a/includes/controllers/ApiController.php +++ b/includes/controllers/ApiController.php @@ -7,6 +7,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Csrf\CsrfTokenManager; use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; use YesWiki\Bazar\Controller\EntryController; use YesWiki\Bazar\Service\EntryManager; @@ -464,6 +465,40 @@ public function deletePageByGetMethod($tag) : new ApiResponse($result, $code); } + /** + * @Route("/api/pages/{tag}/delete/getToken",methods={"GET"},options={"acl":{"public","+"}}) + */ + public function getDeleteToken($tag) + { + return new ApiResponse([ + 'token'=>$this->regenerateToken($tag) + ]); + } + + /** + * use 'example' as tag, to keep same format for route + * @Route("/api/pages/example/delete/getTokens",methods={"GET"},options={"acl":{"public","+"}}) + */ + public function getDeleteTokens() + { + $pageIds = (empty($_GET['pages']) || !is_string($_GET['pages'])) ? [] : explode(',',strval($_GET['pages'])); + $tokens = []; + foreach ($pageIds as $tag) { + $tokens[$tag] = $this->regenerateToken($tag); + } + return new ApiResponse([ + 'tokens'=>$tokens + ]); + } + + protected function regenerateToken(string $tag): string + { + $page = $this->wiki->services->get(PageManager::class)->getOne($tag); + return (!empty($page) && ($this->wiki->UserIsAdmin() || $this->wiki->UserIsOwner($tag))) + ? $this->wiki->services->get(CsrfTokenManager::class)->refreshToken("api\\pages\\$tag\\delete")->getValue() + : 'not-authorized'; + } + /** * @Route("/api/reactions", methods={"GET"}, options={"acl":{"public"}}) */ diff --git a/tools/bazar/actions/BazarCartoAction.php b/tools/bazar/actions/BazarCartoAction.php index ad1055976..69cbd43aa 100755 --- a/tools/bazar/actions/BazarCartoAction.php +++ b/tools/bazar/actions/BazarCartoAction.php @@ -52,11 +52,18 @@ public function formatArguments($arg) // Filters entries via query to remove whose withou bf_latitude nor bf_longitude $query = $this->getService(EntryController::class)->formatQuery($arg, $_GET); - if (!isset($query['bf_latitude!'])) { - $query['bf_latitude!'] = ""; - } - if (!isset($query['bf_longitude!'])) { - $query['bf_longitude!'] = ""; + if ($template != 'map-and-table' || + ( + !empty($arg['tablewith']) && + $arg['tablewith'] === 'only-geolocation' + ) + ) { + if (!isset($query['bf_latitude!'])) { + $query['bf_latitude!'] = ""; + } + if (!isset($query['bf_longitude!'])) { + $query['bf_longitude!'] = ""; + } } return([ diff --git a/tools/bazar/actions/BazarListeAction.php b/tools/bazar/actions/BazarListeAction.php index 3c4fb56ff..0eacfbb33 100755 --- a/tools/bazar/actions/BazarListeAction.php +++ b/tools/bazar/actions/BazarListeAction.php @@ -10,7 +10,8 @@ class BazarListeAction extends YesWikiAction { - protected const BAZARCARTO_TEMPLATES = ["map", "gogomap", "gogocarto"] ; // liste des templates sans .twig ni .tpl.html + protected const BAZARCARTO_TEMPLATES = ["map", "gogomap", "gogocarto","map-and-table"] ; // liste des templates sans .twig ni .tpl.html + protected const BAZARTABLE_TEMPLATES = ["table","map-and-table"] ; // liste des templates sans .twig ni .tpl.html protected const CALENDRIER_TEMPLATES = ["calendar"] ; // liste des templates sans .twig ni .tpl.html protected $debug; @@ -90,12 +91,15 @@ public function formatArguments($arg) } } - if (in_array($template, ['list', 'card'])) { + if (in_array($template, ['list', 'card','map-and-table','table'])) { $dynamic = true; } if ($dynamic && $template == 'liste_accordeon') { $template = 'list'; } + if ($dynamic && in_array($template,['tableau.tpl.html','tableau'])) { + $template = 'table'; + } $searchfields = $this->formatArray($arg['searchfields'] ?? null); $searchfields = empty($searchfields) ? ['bf_titre'] : $searchfields; // End dynamic @@ -225,11 +229,14 @@ public function run() // If the template is a map or a calendar, call the dedicated action so that // arguments can be properly formatted. The second first condition prevents infinite loops if (self::specialActionFromTemplate($this->arguments['template'], "BAZARCARTO_TEMPLATES") - && (!isset($this->arguments['calledBy']) || $this->arguments['calledBy'] !== 'BazarCartoAction')) { + && (!isset($this->arguments['calledBy']) || !in_array($this->arguments['calledBy'],['BazarCartoAction','BazarTableAction']))) { return $this->callAction('bazarcarto', $this->arguments); } elseif (self::specialActionFromTemplate($this->arguments['template'], "CALENDRIER_TEMPLATES") && (!isset($this->arguments['calledBy']) || $this->arguments['calledBy'] !== 'CalendrierAction')) { return $this->callAction('calendrier', $this->arguments); + } elseif (self::specialActionFromTemplate($this->arguments['template'], "BAZARTABLE_TEMPLATES") + && (!isset($this->arguments['calledBy']) || $this->arguments['calledBy'] !== 'BazarTableAction')) { + return $this->callAction('bazartable', $this->arguments); } $bazarListService = $this->getService(BazarListService::class); @@ -239,9 +246,11 @@ public function run() if (isset($this->arguments['zoom'])) { $this->arguments['zoom'] = intval($this->arguments['zoom']) ; } + $currentUser = $this->getService(AuthController::class)->getLoggedUser(); return $this->render("@bazar/entries/index-dynamic-templates/{$this->arguments['template']}.twig", [ 'params' => $this->arguments, 'forms' => count($this->arguments['idtypeannonce']) === 0 ? $forms : '', + 'currentUserName' => empty($currentUser['name']) ? '' : $currentUser['name'] ]); } else { $entries = $bazarListService->getEntries($this->arguments, $forms); @@ -352,6 +361,9 @@ public static function specialActionFromTemplate(string $templateName, string $c case "CALENDRIER_TEMPLATES": $baseArray = self::CALENDRIER_TEMPLATES ; break; + case "BAZARTABLE_TEMPLATES": + $baseArray = self::BAZARTABLE_TEMPLATES ; + break; default: return false; } diff --git a/tools/bazar/actions/BazarTableAction.php b/tools/bazar/actions/BazarTableAction.php new file mode 100644 index 000000000..6dcb42c6f --- /dev/null +++ b/tools/bazar/actions/BazarTableAction.php @@ -0,0 +1,39 @@ +appendAllFieldsIds($arg,$newArg,'columnfieldsids'); + } elseif ($this->formatBoolean($arg,false,'exportallcolumns')){ + $this->appendAllFieldsIds($arg,$newArg,'exportallcolumnsids'); + } + + return($newArg); + } + + public function run() + { + return $this->callAction('bazarliste', $this->arguments); + } + + protected function appendAllFieldsIds(array $arg, array &$newArg,string $key){ + $formId = empty($arg['id']) ? '1' : array_values(array_filter(explode(',',$arg['id']),function($id){ + return strval($id) == strval(intval($id)); + }))[0]; + $form = $this->getService(FormManager::class)->getOne($formId); + if (!empty($form['prepared'])){ + $newArg[$key] = implode(',',array_map(function($field){ + return $field->getPropertyName(); + },array_filter($form['prepared'],function($field){ + return !empty($field->getPropertyName()); + }))); + } + } +} diff --git a/tools/bazar/presentation/javascripts/components/BazarTable.js b/tools/bazar/presentation/javascripts/components/BazarTable.js new file mode 100644 index 000000000..731b52228 --- /dev/null +++ b/tools/bazar/presentation/javascripts/components/BazarTable.js @@ -0,0 +1,767 @@ +import SpinnerLoader from './SpinnerLoader.js' + +let componentName = 'BazarTable'; +let isVueJS3 = (typeof Vue.createApp == "function"); + +let componentParams = { + props: ['currentusername','params','entries','ready','root','isadmin'], + components: {SpinnerLoader}, + data: function() { + return { + cacheResolveReject: {}, + columns: [], + dataTable: null, + displayedEntries: {}, + fields: {}, + forms: {}, + isReady:{ + fields: false, + params: false + }, + templatesForRendering: {}, + uuid: null + }; + }, + methods:{ + addRows(dataTable,columns,entries,currentusername,isadmin){ + const entriesToAdd = entries.filter((entry)=>typeof entry === 'object' && 'id_fiche' in entry && !(entry.id_fiche in this.displayedEntries)) + let formattedDataList = [] + entriesToAdd.forEach((entry)=>{ + this.displayedEntries[entry.id_fiche] = entry + let formattedData = {} + columns.forEach((col)=>{ + if (!(typeof col.data === 'string')){ + formattedData[col.data] = '' + } else if (col.data === '==canDelete=='){ + formattedData[col.data] = !this.$root.isExternalUrl(entry) && + 'owner' in entry && + (isadmin || entry.owner == currentusername) + } else if (['==adminsbuttons=='].includes(col.data)) { + formattedData[col.data] = '' + } else if ('firstlevel' in col && typeof col.firstlevel === 'string' && col.firstlevel.length > 0){ + formattedData[col.data] = ( + col.firstlevel in entry && + (typeof entry[col.firstlevel] === 'object') && + entry[col.firstlevel] !== null && + col.data in entry[col.firstlevel] + ) ? entry[col.firstlevel][col.data] : '' + } else if ('checkboxfield' in col && typeof col.checkboxfield === 'string' && col.checkboxfield.length > 0){ + formattedData[col.data] = ( + col.checkboxfield in entry && + 'checkboxkey' in col && + typeof entry[col.checkboxfield] === 'string' + && entry[col.checkboxfield].split(',').includes(col.checkboxkey) + ) ? 'X' : '' + } else if ('displayValOptions' in col){ + formattedData[col.data] = (col.data in entry && typeof entry[col.data] === 'string') ? { + display:entry[col.data].split(',').map((v)=>v in col.displayValOptions ? col.displayValOptions[v] : v).join(",\n"), + export: entry[col.data].split(',').map((v)=>v in col.displayValOptions ? `"${col.displayValOptions[v]}"` : v).join(',') + } : '' + } else { + formattedData[col.data] = (col.data in entry && typeof entry[col.data] === 'string' ) ? entry[col.data] : '' + } + }); + ['id_fiche','color','icon','url'].forEach((key)=>{ + if (!(key in formattedData)){ + formattedData[key] = entry[key] || '' + } + }) + formattedDataList.push(formattedData) + }) + dataTable.rows.add(formattedDataList) + }, + arraysEqual(a, b) { + if (a === b) return true + if (a == null || b == null || !Array.isArray(a) || !Array.isArray(b)) return false + if (a.length !== b.length) return false + + a.sort() + b.sort() + return a.every((val,idx)=>a[idx] !== b[idx]) + }, + deleteAllSelected(event){ + const uuid = this.getUuid() + multiDeleteService.updateNbSelected(`MultiDeleteModal${uuid}`) + const entriesIdsToRefreshDeleteToken = [] + $(`#${uuid}`).find('tr > td:first-child input.selectline[type=checkbox]:visible:checked').each(function (){ + const csrfToken = $(this).data('csrftoken') + const itemId = $(this).data('itemid') + if (typeof itemId === 'string' && itemId.length > 0 && (typeof csrfToken !== 'string' || csrfToken === 'to-be-defined')){ + entriesIdsToRefreshDeleteToken.push({elem:$(this),itemId}) + } + }) + if (entriesIdsToRefreshDeleteToken.length > 0){ + this.getCsrfDeleteTokens(entriesIdsToRefreshDeleteToken.map((e)=>e.itemId)) + .then((tokens)=>{ + entriesIdsToRefreshDeleteToken.forEach(({elem,itemId})=>{ + $(elem).data('csrftoken',tokens[itemId] || 'error') + }) + }) + .catch(this.manageError) + } + // if something to do before showing modal (like get csrf token ?) + }, + getAdminsButtons(entryId,entryTitle,entryUrl,candelete){ + const isExternal =this.$root.isExternalUrl({id_fiche:entryId,url:entryUrl}) + return this.getTemplateFromSlot( + 'adminsbuttons', + { + entryId:'entryId', + entryTitle:'entryTitle', + entryUrl:'entryUrl', + isExternal, + candelete: [true,'true'].includes(candelete) + } + ).replace(/entryId/g,entryId) + .replace(/entryTitle/g,entryTitle) + .replace(/entryUrl/g,entryUrl) + }, + async getColumns(){ + if (this.columns.length == 0){ + const fields = await this.waitFor('fields') + const params = await this.waitFor('params'); + let columnfieldsids = this.sanitizedParam(params,this.isAdmin,'columnfieldsids') + let defaultcolumnwidth = this.sanitizedParam(params,this.isAdmin,'defaultcolumnwidth') + if (columnfieldsids.every((id)=>id.length ==0)){ + // backup + columnfieldsids = ['bf_titre'] + } + const data = {columns:[]} + const width = defaultcolumnwidth.length > 0 ? {width:defaultcolumnwidth}: {} + if (this.sanitizedParam(params,this.isAdmin,'displayadmincol')){ + const uuid = this.getUuid() + data.columns.push({ + ...{ + data: '==canDelete==', + class: 'not-export-this-col', + orderable: false, + render: (data,type,row)=>{ + return type === 'display' ? this.getDeleteChekbox(uuid,row.id_fiche,!data) : '' + }, + title: this.getDeleteChekboxAll(uuid,'top'), + footer: this.getDeleteChekboxAll(uuid,'bottom') + }, + ...width + }) + data.columns.push({ + ...{ + data: '==adminsbuttons==', + orderable: false, + class: 'horizontal-admins-btn not-export-this-col', + render: (data,type,row)=>{ + return type === 'display' ? this.getAdminsButtons(row.id_fiche,row.bf_titre || '',row.url || '',row['==canDelete==']) : '' + }, + title: '', + footer: '' + }, + ...width + }) + } + const options={ + checkboxfieldsincolumns: this.sanitizedParam(params,this.isAdmin,'checkboxfieldsincolumns'), + columnswidth: this.sanitizedParam(params,this.isAdmin,'columnswidth'), + defaultcolumnwidth, + sumfieldsids: this.sanitizedParam(params,this.isAdmin,'sumfieldsids'), + visible:true, + printable:true, + addLink:false, + columntitles:this.sanitizedParam(params,this.isAdmin,'columntitles'), + displayimagesasthumbnails:this.sanitizedParam(params,this.isAdmin,'displayimagesasthumbnails'), + displayvaluesinsteadofkeys:this.sanitizedParam(params,this.isAdmin,'displayvaluesinsteadofkeys'), + baseIdx: data.columns.length + } + columnfieldsids.forEach((id,idx)=>{ + if (id.length >0 && id in fields){ + this.registerField(data,{ + ...options, + ...{ + field:fields[id], + visible:true, + printable:true, + addLink:idx === 0 && !('bf_titre' in columnfieldsids) + } + }) + } + }) + if (await this.sanitizedParamAsync('exportallcolumns')){ + Object.keys(fields).forEach((id)=>{ + // append fields not displayed + if (!columnfieldsids.includes(id)){ + this.registerField(data,{ + ...options, + ...{ + field:fields[id], + visible:false, + printable:false, + addLink:false + } + }) + } + }) + } + + [ + ['displaycreationdate','date_creation_fiche','creationdatetranslate'], + ['displaylastchangedate','date_maj_fiche','modifiydatetranslate'], + ['displayowner','owner','ownertranslate'] + ].forEach(([paramName,propertyName,slotName])=>{ + if (this.sanitizedParam(params,this.isadmin,paramName)){ + data.columns.push({ + data: propertyName, + title: this.getTemplateFromSlot(slotName,{}), + footer: '' + }) + } + }) + this.columns = data.columns + } + return this.columns + }, + async getCsrfDeleteToken(entryId){ + return await this.getJson(wiki.url(`?api/pages/${entryId}/delete/getToken`)) + .then((json)=>('token' in json && typeof json.token === 'string') ? json.token : 'error') + }, + async getCsrfDeleteTokens(entriesIds){ + return await this.getJson(wiki.url(`?api/pages/example/delete/getTokens`,{pages:entriesIds.join(',')})) + .then((json)=>('tokens' in json && typeof json.tokens === 'object') ? json.tokens : entriesIds.reduce((o, key) => ({ ...o, [key]: 'error'}), {})) + }, + getDatatableOptions(){ + const buttons = [] + DATATABLE_OPTIONS.buttons.forEach((option) => { + buttons.push({ + ...option, + ...{ footer: true }, + ...{ + exportOptions: ( + option.extend != 'print' + ? { + orthogonal: 'sort', // use sort data for export + columns(idx, data, node) { + return !$(node).hasClass('not-export-this-col') + } + } + : { + columns(idx, data, node) { + const isVisible = $(node).data('visible') + return !$(node).hasClass('not-export-this-col') && ( + isVisible == undefined || isVisible != false + ) && !$(node).hasClass('not-printable') + } + }) + } + }) + }) + return { + ...DATATABLE_OPTIONS, + ...{ + searching: false, + footerCallback: ()=>{ + this.updateFooter() + }, + buttons + } + } + }, + async getDatatable(){ + if (this.dataTable === null){ + // create dataTable + const columns = await this.getColumns() + const sumfieldsids = await this.sanitizedParamAsync('sumfieldsids') + let firstColumnOrderable = 0 + for (let index = 0; index < columns.length; index++) { + if ('orderable' in columns[index] && !columns[index].orderable){ + firstColumnOrderable += 1 + } else { + break + } + } + this.dataTable = $(this.$refs.dataTable).DataTable({ + ...this.getDatatableOptions(), + ...{ + columns: columns, + "scrollX": true, + order: [[firstColumnOrderable,'asc']] + } + }) + $(this.dataTable.table().node()).prop('id',this.getUuid()) + if (sumfieldsids.length > 0 || this.sanitizedParamAsync('displayadmincol')){ + this.initFooter(columns,sumfieldsids) + } + } + return this.dataTable + }, + getDeleteChekbox(targetId,itemId,disabled = false){ + return this.getTemplateFromSlot( + 'deletecheckbox', + {targetId:'targetId',itemId:'itemId',disabled} + ).replace(/targetId/g,targetId) + .replace(/itemId/g,itemId) + }, + getDeleteChekboxAll(targetId,selectAllType){ + return this.getTemplateFromSlot('deletecheckboxall',{}) + .replace(/targetId/g,targetId) + .replace(/selectAllType/g,selectAllType) + }, + async getJson(url){ + return await fetch(url) + .then((response)=>{ + if (response.ok){ + return response.json() + } else { + throw new Error(`reponse was not ok when getting "${url}"`) + } + }) + }, + getTemplateFromSlot(name,params){ + const key = name+'-'+JSON.stringify(params) + if (!(key in this.templatesForRendering)){ + if (name in this.$scopedSlots){ + const slot = this.$scopedSlots[name] + const constructor = Vue.extend({ + render: function(h){ + return h('div',{},slot(params)) + } + }) + const instance = new constructor() + instance.$mount() + let outerHtml = ''; + for (let index = 0; index < instance.$el.childNodes.length; index++) { + outerHtml += instance.$el.childNodes[index].outerHTML || instance.$el.childNodes[index].textContent + } + this.templatesForRendering[key] = outerHtml + } else { + this.templatesForRendering[key] = '' + } + } + return this.templatesForRendering[key] + }, + getUuid(){ + if (this.uuid === null){ + this.uuid = crypto.randomUUID() + } + return this.uuid + }, + initFooter(columns,sumfieldsids){ + const footerNode = this.dataTable.footer().to$() + if (footerNode[0] !== null){ + const footer = $('') + let displayTotal = sumfieldsids.length > 0 + columns.forEach((col)=>{ + if ('footer' in col && col.footer.length > 0){ + const element = $(col.footer) + const isTh = $(element).prop('tagName') === 'TH' + footer.append(isTh ? element : $('').append(element)) + } else if (displayTotal) { + displayTotal = false + footer.append($('').text(this.getTemplateFromSlot('sumtranslate',{}))) + } else { + footer.append($('')) + } + }) + footerNode.html(footer) + } + }, + manageError(error){ + if (wiki.isDebugEnabled){ + console.error(error) + } + return null + }, + registerField(data,{ + field, + checkboxfieldsincolumns=false, + sumfieldsids=[], + visible=true, + printable=true, + addLink=false, + columnswidth={}, + defaultcolumnwidth='', + columntitles={}, + baseIdx=0, + displayimagesasthumbnails=false, + displayvaluesinsteadofkeys=false + }){ + if (typeof field.propertyname === 'string' && field.propertyname.length > 0){ + const className = (printable ? '' : 'not-printable')+(sumfieldsids.includes(field.propertyname) ? ' sum-activated': '') + const width = field.propertyname in columnswidth ? {width:columnswidth[field.propertyname]} : (defaultcolumnwidth.length > 0 ? {width:defaultcolumnwidth}: {}) + const titleIdx = data.columns.length - baseIdx + if (typeof field.type === 'string' && field.type === 'map'){ + data.columns.push({ + ...{ + class: className, + data: field.latitudeField, + title: columntitles[field.latitudeField] || columntitles[titleIdx] || this.getTemplateFromSlot('latitudetext',{}), + firstlevel: field.propertyname, + render: this.renderCell({addLink,idx:titleIdx}), + footer: '', + visible + }, + ...width + }) + data.columns.push({ + ...{ + class: className, + data: field.longitudeField, + title: columntitles[field.longitudeField] || columntitles[titleIdx+1] || this.getTemplateFromSlot('longitudetext',{}), + firstlevel: field.propertyname, + render: this.renderCell({addLink}), + footer: '', + visible + }, + ...width + }) + } else if (checkboxfieldsincolumns && + typeof field.type === 'string' && + ['checkboxfiche','checkbox'].includes(field.type) && + typeof field.options == 'object') { + Object.keys(field.options).forEach((optionKey,idx)=>{ + const name = `${field.propertyname}-${optionKey}` + data.columns.push({ + ...{ + class: className, + data: name, + title: columntitles[name] || + ( + field.propertyname in columntitles + ? `${columntitles[field.propertyname]} - ${field.options[optionKey] || optionKey}` + : undefined + ) || + columntitles[titleIdx+idx] || + `${field.label || field.propertyname} - ${field.options[optionKey] || optionKey}`, + checkboxfield: field.propertyname, + render: this.renderCell({addLink,idx:titleIdx+idx}), + checkboxkey: optionKey, + footer: '', + visible + }, + ...width + }) + }) + } else { + const fieldtype = ['link','email'].includes(field.type) ? field.type: ((field.type === 'image' && displayimagesasthumbnails)?'image':'') + const fieldName = (fieldtype === 'image' && displayimagesasthumbnails) ? field.propertyname : '' + const displayValOptions = (displayvaluesinsteadofkeys && 'options' in field && typeof field.options === 'object') + ? { + displayValOptions:field.options + } + : {} + data.columns.push({ + ...{ + class: className, + data: field.propertyname, + title: columntitles[field.propertyname] || columntitles[titleIdx] || field.label || field.propertyname, + render: this.renderCell({fieldtype,addLink,idx:titleIdx,fieldName}), + footer: '', + visible + }, + ...width, + ...displayValOptions + }) + } + } + }, + removeRows(dataTable,newIds){ + let entryIdsToRemove = Object.keys(this.displayedEntries).filter((id)=>!newIds.includes(id)) + entryIdsToRemove.forEach((id)=>{ + if (id in this.displayedEntries){ + delete this.displayedEntries[id] + } + }) + dataTable.rows((idx,data,node)=>{ + return !('id_fiche' in data) || entryIdsToRemove.includes(data.id_fiche) + }).remove() + }, + resolve(name){ + this.isReady[name] = true + if (name in this.cacheResolveReject && + Array.isArray(this.cacheResolveReject[name])){ + const listOfResolveReject = this.cacheResolveReject[name] + this.cacheResolveReject[name] = [] + listOfResolveReject.forEach(({resolve})=>resolve(name in this ? this[name] : null)) + } + }, + renderCell({fieldtype='',fieldName='',addLink=false,idx=-1}){ + return (data,type,row)=>{ + if (type === 'sort' || type === 'filter'){ + return (typeof data === 'object' && 'export' in data) ? data.export : data + } + const formattedData = (typeof data === 'object' && data !== null && 'display' in data) ? data.display : (data === null ? '' : String(data)) + let anchorData = 'anchorData' + let anchorImageSpecificPart = '' + let anchorImageOther = '' + let anchorImageExt = '' + let anchorOtherEntryId = '' + if (fieldtype === 'image'){ + if(formattedData.length > 0){ + let regExp = new RegExp(`^(${row.id_fiche}_${fieldName}_)(.*)_(\\d{14})_(\\d{14})\\.([^.]+)$`) + if (regExp.test(formattedData)) { + let anchorImageDate1 = '' + let anchorImageDate2 = ''; + [,,anchorImageSpecificPart,anchorImageDate1,anchorImageDate2,anchorImageExt] = formattedData.match(regExp) + anchorImageOther = `${anchorImageDate1}_${anchorImageDate2}` + anchorData = 'entryIdAnchor_fieldNameAnchor_anchorImageSpecificPart_anchorImageOther.anchorImageExt' + } else { + regExp = new RegExp(`^(${row.id_fiche}_${fieldName}_)(.*)\\.([^.]+)$`) + if (regExp.test(formattedData)) { + [,,anchorImageSpecificPart,anchorImageExt] = formattedData.match(regExp) + anchorData = 'entryIdAnchor_fieldNameAnchor_anchorImageSpecificPart.anchorImageExt' + } else { + // maybe from other entry + regExp = new RegExp(`^([A-Za-z0-9-_]+)(_${fieldName}_)(.*)_(\\d{14})_(\\d{14})\\.([^.]+)$`) + if (regExp.test(formattedData)) { + let anchorImageDate1 = '' + let anchorImageDate2 = ''; + [,anchorOtherEntryId,,anchorImageSpecificPart,anchorImageDate1,anchorImageDate2,anchorImageExt] = formattedData.match(regExp) + anchorImageOther = `${anchorImageDate1}_${anchorImageDate2}` + anchorData = 'anchorOtherEntryId_fieldNameAnchor_anchorImageSpecificPart_anchorImageOther.anchorImageExt' + } else { + // last possible format + regExp = new RegExp('^(.*)\\.([^.]+)$') + if (regExp.test(formattedData)) { + [,anchorImageSpecificPart,anchorImageExt] = formattedData.match(regExp) + anchorData = 'anchorImageSpecificPart.anchorImageExt' + } else { + anchorImageSpecificPart = formattedData + anchorData = 'anchorImageSpecificPart' + } + } + } + } + } else { + anchorData = '' + } + } + const template = this.getTemplateFromSlot('rendercell',{ + anchorData, + fieldtype, + addLink, + entryId:'entryIdAnchor', + fieldName, + url:'anchorUrl', + color: (idx === 0 && row.color.length > 0) ? 'lightslategray' : '', + icon: (idx === 0 && row.icon.length > 0) ? 'iconAnchor' : '' + }) + return template.replace(/anchorData/g,formattedData.replace(/\n/g,'
')) + .replace(/entryIdAnchor/g,row.id_fiche) + .replace(/anchorUrl/g,row.url) + .replace(/lightslategray/g,row.color) + .replace(/iconAnchor/g,row.icon) + .replace(/fieldNameAnchor/g,fieldName) + .replace(/anchorImageSpecificPart/g,anchorImageSpecificPart) + .replace(/anchorImageOther/g,anchorImageOther) + .replace(/anchorImageExt/g,anchorImageExt) + .replace(/anchorOtherEntryId/g,anchorOtherEntryId) + } + }, + async sanitizedParamAsync(name){ + return this.sanitizedParam(await this.waitFor('params'),this.isadmin,name) + }, + sanitizedParam(params,isAdmin,name){ + switch (name) { + case 'displayadmincol': + case 'displaycreationdate': + case 'displaylastchangedate': + case 'displayowner': + const paramValue = ( + name in params && + typeof params[name] === 'string' && + ['yes','onlyadmins'].includes(params[name])) + ? params[name] + : false + switch (paramValue) { + case 'onlyadmins': + return [1,true,'1','true'].includes(isAdmin) + case 'yes': + return true + case false: + default: + return false + } + case 'checkboxfieldsincolumns': + // default true + return name in params ? !([false,0,'0','false'].includes(params[name])) : true + case 'displayvaluesinsteadofkeys': + case 'exportallcolumns': + case 'displayimagesasthumbnails': + // default false + return name in params ? [true,1,'1','true'].includes(params[name]) : false + + case 'columnfieldsids': + case 'sumfieldsids': + return (name in params && typeof params[name] === 'string') + ? params[name].split(',').map((v)=>v.trim()) + : [] + case 'columntitles': + const columntitlesastab = (name in params && typeof params[name] === 'string') + ? params[name].split(',').map((v)=>v.trim()) + : [] + const columntitles = {} + columntitlesastab.forEach((val,idx)=>{ + const match = val.match(/^([A-Za-z0-9\-_]+)=(.+$)/) + if (match){ + const [,key,title] = match + columntitles[key] = title + } else { + columntitles[idx] = val + } + }) + return columntitles + case 'sumfieldsids': + return (name in params && typeof params[name] === 'string') + ? params[name].split(',').map((v)=>v.trim()) + : [] + case 'columnswidth': + const columnswidth = {} + if ( + name in params && + typeof params[name] === 'string' + ) { + params[name].split(',').forEach((extract)=>{ + const [name,value] = extract.split('=',2) + if (name && value && name.length > 0 && value.length > 0){ + columnswidth[name] = value + } + }) + } + return columnswidth + case 'defaultcolumnwidth': + return name in params ? String(params[name]) : '' + default: + return params[name] || null + } + }, + sanitizeValue(val) { + let sanitizedValue = val + if (Object.prototype.toString.call(val) === '[object Object]') { + // because if orthogonal data is defined, value is an object + sanitizedValue = val.display || '' + } + return (isNaN(sanitizedValue)) ? 1 : Number(sanitizedValue) + }, + startDelete(event){ + if (!multiDeleteService.isRunning) { + multiDeleteService.isRunning = true + const elem = event.target + if (elem) { + $(elem).attr('disabled', 'disabled') + multiDeleteService.deleteItems(elem) + } + } + }, + async updateEntries(newEntries,newIds){ + const columns = await this.getColumns() + const dataTable = await this.getDatatable() + const currentusername = this.currentusername + this.removeRows(dataTable,newIds) + this.addRows(dataTable,columns,newEntries,currentusername,this.isadmin) + this.dataTable.draw() + }, + updateFieldsFromRoot(){ + this.fields = this.$root.formFields + if (Object.keys(this.fields).length > 0){ + this.resolve('fields') + } + }, + updateFooter(){ + if (this.dataTable !== null){ + const activatedRows = [] + this.dataTable.rows({ search: 'applied' }).every(function() { + activatedRows.push(this.index()) + }) + this.dataTable.columns('.sum-activated').every((indexCol) => { + let col = this.dataTable.column(indexCol) + let sum = 0 + activatedRows.forEach((indexRow) => { + const value = this.dataTable.row(indexRow).data()[col.dataSrc()] + sum += this.sanitizeValue(Number(value)) + }) + this.dataTable.footer().to$().find(`> tr > th:nth-child(${indexCol+1})`).html(sum) + }) + } + }, + async waitFor(name){ + if (this.isReady[name]){ + return this[name] || null + } + if (!(name in this.cacheResolveReject)){ + this.cacheResolveReject[name] = [] + } + const promise = new Promise((resolve,reject)=>{ + this.cacheResolveReject[name].push({resolve,reject}) + }) + return await promise.then((...args)=>Promise.resolve(...args)) // force .then() + } + }, + mounted(){ + $(isVueJS3 ? this.$el.parentNode : this.$el).on('dblclick',function(e) { + return false; + }); + this.updateFieldsFromRoot() + window.urlImageResizedOnError = this.$root.urlImageResizedOnError + }, + watch: { + entries(newVal, oldVal) { + this.updateFieldsFromRoot() // because updated in same time than entries (but not reactive) + const sanitizedNewVal = newVal.filter((e)=>(typeof e === 'object' && e !== null && 'id_fiche' in e)) + const newIds = sanitizedNewVal.map((e) => e.id_fiche) + const oldIds = oldVal.map((e) => e.id_fiche || '').filter((e)=>(typeof e === 'string' && e.length > 0)) + if (!this.arraysEqual(newIds, oldIds)) { + this.updateEntries(sanitizedNewVal,newIds).catch(this.manageError) + } + }, + params() { + this.resolve('params') + }, + ready(){ + this.sanitizedParamAsync('displayadmincol').then((displayadmincol)=>{ + if (displayadmincol){ + $(this.$refs.buttondeleteall).find(`#MultiDeleteModal${this.getUuid()}`).first().each(function(){ + $(this).on('shown.bs.modal', function() { + multiDeleteService.initProgressBar($(this)) + $(this).find('.modal-body .multi-delete-results').html('') + $(this).find('button.start-btn-delete-all').removeAttr('disabled') + }) + $(this).on('hidden.bs.modal', function() { + multiDeleteService.modalClosing($(this)) + }) + }) + } + }).catch(this.manageError) + } + }, + template: ` +
+ + + + + +
+
+ +
+ + +
+ ` +}; + +if (isVueJS3){ + if (window.hasOwnProperty('bazarVueApp')){ // bazarVueApp must be defined into bazar-list-dynamic + if (!bazarVueApp.config.globalProperties.hasOwnProperty('wiki')){ + bazarVueApp.config.globalProperties.wiki = wiki; + } + if (!bazarVueApp.config.globalProperties.hasOwnProperty('_t')){ + bazarVueApp.config.globalProperties._t = _t; + } + window.bazarVueApp.component(componentName,componentParams); + } +} else { + if (!Vue.prototype.hasOwnProperty('wiki')){ + Vue.prototype.wiki = wiki; + } + if (!Vue.prototype.hasOwnProperty('_t')){ + Vue.prototype._t = _t; + } + Vue.component(componentName,componentParams); +} \ No newline at end of file diff --git a/tools/bazar/presentation/javascripts/components/BazarTableEntrySelector.js b/tools/bazar/presentation/javascripts/components/BazarTableEntrySelector.js new file mode 100644 index 000000000..6f07ef1b2 --- /dev/null +++ b/tools/bazar/presentation/javascripts/components/BazarTableEntrySelector.js @@ -0,0 +1,27 @@ +Vue.component('BazarTableEntrySelector', { + props: ['params', 'entries'], + data() { + return {} + }, + computed: { + entriesToDisplay(){ + return (this.params !== null && typeof this.params === 'object' && 'tablewith' in this.params && this.params.tablewith === 'no-geolocation') + ? this.entries.filter((e)=>typeof e === 'object' && e !== null && ( + !('bf_latitude' in e) || + !('bf_longitude' in e) || + e.bf_latitude === null || + e.bf_longitude === null || + String(e.bf_longitude).length === 0 || + String(e.bf_latitude).length === 0 || + Number(e.bf_longitude) === 0 || + Number(e.bf_latitude) === 0 + )) + : this.entries + } + }, + template: ` +
+ +
+ ` +}) \ No newline at end of file diff --git a/tools/bazar/templates/entries/index-dynamic-templates/map-and-table.twig b/tools/bazar/templates/entries/index-dynamic-templates/map-and-table.twig new file mode 100644 index 000000000..d03c2a40f --- /dev/null +++ b/tools/bazar/templates/entries/index-dynamic-templates/map-and-table.twig @@ -0,0 +1,20 @@ +{% set necessary_fields = (necessary_fields is defined ? necessary_fields : []) + |merge(['bf_latitude', 'bf_longitude']) + %} + +{% extends "@bazar/entries/index-dynamic-templates/table.twig" %} + +{% block assets %} + {{ block('assets','@bazar/entries/index-dynamic-templates/map.twig') }} + {{ parent() }} {# from table.twig #} + {{ include_javascript('tools/bazar/presentation/javascripts/components/BazarTableEntrySelector.js', false, true) }} +{% endblock %} + +{% block display_entries %} + {{ block("display_entries",'@bazar/entries/index-dynamic-templates/map.twig') }} + + + +{% endblock %} \ No newline at end of file diff --git a/tools/bazar/templates/entries/index-dynamic-templates/table.twig b/tools/bazar/templates/entries/index-dynamic-templates/table.twig new file mode 100644 index 000000000..75ddd741c --- /dev/null +++ b/tools/bazar/templates/entries/index-dynamic-templates/table.twig @@ -0,0 +1,140 @@ +{% import "@core/multidelete-macro.twig" as multidelete %} + +{% extends "@bazar/entries/index-dynamic.twig" %} + +{% set necessary_fields = (necessary_fields is defined ? necessary_fields : (params.necessary_fields is defined ? params.necessary_fields|split(',') : [])) + |merge(['owner']) + |merge(( + (params.exportallcolumns is defined and (params.exportallcolumns == '1' or params.exportallcolumns == 'true')) + ? params.exportallcolumnsids|split(',') + : (params.columnfieldsids is defined + ? params.columnfieldsids|split(',') + : [] + ) + )) + |merge((params.displaycreationdate is defined and (params.displaycreationdate != 'no'))? ['date_creation_fiche']: []) + |merge((params.displaylastchangedate is defined and (params.displaycreationdate != 'no'))? ['date_maj_fiche']: []) + %} + +{% block assets %} + {{ include_css('styles/vendor/datatables-full/dataTables.bootstrap.min.css') }} + {{ include_css('tools/bazar/presentation/styles/tableau.css') }} + + {{ include_javascript('javascripts/vendor/datatables-full/jquery.dataTables.min.js') }} + {{ include_javascript('tools/bazar/presentation/javascripts/components/BazarTable.js', false, true) }} + +{% endblock %} + +{% block display_entries %} + {% set imWidth = 150 %} + {% set imHeight = 100 %} + {% set firstTokenCrop = csrfToken("GET api/images/cache/#{imWidth}/#{imHeight}/crop") %} + + + + + + + + + + + + + + + + +{% endblock %} \ No newline at end of file From d80862b6f08bbdc7df7a86c00bb071ec6362838b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Fri, 28 Apr 2023 17:21:40 +0200 Subject: [PATCH 045/152] fix(BazarTableAction): remove `pagination` for this templates --- tools/bazar/actions/BazarTableAction.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/bazar/actions/BazarTableAction.php b/tools/bazar/actions/BazarTableAction.php index 6dcb42c6f..8d0f9df5e 100644 --- a/tools/bazar/actions/BazarTableAction.php +++ b/tools/bazar/actions/BazarTableAction.php @@ -9,6 +9,7 @@ class BazarTableAction extends YesWikiAction public function formatArguments($arg) { $newArg = []; + $newArg['pagination'] = -1; if (empty($arg['columnfieldsids'])){ $this->appendAllFieldsIds($arg,$newArg,'columnfieldsids'); } elseif ($this->formatBoolean($arg,false,'exportallcolumns')){ From 8a945bb76579fcbe61fa1d9e5ea92fe211fe2a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Sat, 29 Apr 2023 11:03:48 +0200 Subject: [PATCH 046/152] fix(BazarCarto): do not try explode array from args --- tools/bazar/actions/BazarCartoAction.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/bazar/actions/BazarCartoAction.php b/tools/bazar/actions/BazarCartoAction.php index 69cbd43aa..5075ce253 100755 --- a/tools/bazar/actions/BazarCartoAction.php +++ b/tools/bazar/actions/BazarCartoAction.php @@ -80,7 +80,7 @@ public function formatArguments($arg) * Exemple: provider="OpenStreetMap.France" providers="OpenStreetMap.Mapnik,OpenStreetMap.France" * TODO: ajouter gestion "providers_credentials" */ - 'providers' => isset($arg['providers']) ? explode(',', $arg['providers']) : [], + 'providers' => $this->formatArray($arg['providers'] ?? []), /* * Une liste de layers (couches). * Exemple avec 1 layer tiles, 1 layer geojson: @@ -95,7 +95,7 @@ public function formatArguments($arg) * Le plus simple est de recopier les data GeoJson dans une page du Wiki puis de l'appeler avec le handler "/raw". * TODO: ajouter gestion "layers_credentials" */ - 'layers' => isset($arg['layers']) ? explode(',', $arg['layers']) : [], + 'layers' => $this->formatArray($arg['layers'] ?? []), // Mettre des puces petites ? non par defaut 'markersize' => $markerSize, 'smallmarker' => $smallMarker === '1' ? '' : ' xl', From 783313e8fa89ab7caefc4856b47a4c2fd341eb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Sat, 29 Apr 2023 11:04:03 +0200 Subject: [PATCH 047/152] fix(table.twig): set img sier --- .../bazar/templates/entries/index-dynamic-templates/table.twig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/bazar/templates/entries/index-dynamic-templates/table.twig b/tools/bazar/templates/entries/index-dynamic-templates/table.twig index 75ddd741c..4d9f04d12 100644 --- a/tools/bazar/templates/entries/index-dynamic-templates/table.twig +++ b/tools/bazar/templates/entries/index-dynamic-templates/table.twig @@ -129,6 +129,8 @@ From e7885313f27cc711b66ccab148bf0fddfc2941dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Thu, 11 May 2023 11:55:36 +0200 Subject: [PATCH 048/152] feat(BazarTable): take in account fields `champ` and `ordre` --- .../javascripts/components/BazarTable.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/bazar/presentation/javascripts/components/BazarTable.js b/tools/bazar/presentation/javascripts/components/BazarTable.js index 731b52228..12e35e2fa 100644 --- a/tools/bazar/presentation/javascripts/components/BazarTable.js +++ b/tools/bazar/presentation/javascripts/components/BazarTable.js @@ -267,6 +267,9 @@ let componentParams = { // create dataTable const columns = await this.getColumns() const sumfieldsids = await this.sanitizedParamAsync('sumfieldsids') + const champField = await this.sanitizedParamAsync('champ') + const ordreField = await this.sanitizedParamAsync('ordre') + let order = (ordreField === 'desc') ? 'desc' :'asc' let firstColumnOrderable = 0 for (let index = 0; index < columns.length; index++) { if ('orderable' in columns[index] && !columns[index].orderable){ @@ -275,12 +278,20 @@ let componentParams = { break } } + let columnToOrder = firstColumnOrderable + if (typeof champField === 'string' && champField.length > 0){ + for (let index = 0; index < columns.length; index++) { + if ((!('orderable' in columns[index]) || columns[index].orderable) && columns[index].data == champField){ + columnToOrder = index + } + } + } this.dataTable = $(this.$refs.dataTable).DataTable({ ...this.getDatatableOptions(), ...{ columns: columns, "scrollX": true, - order: [[firstColumnOrderable,'asc']] + order: [[columnToOrder,order]] } }) $(this.dataTable.table().node()).prop('id',this.getUuid()) From 294ba98141dacf50142d1479e641edfe25d6f70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Thu, 11 May 2023 12:36:04 +0200 Subject: [PATCH 049/152] feat(BazarTable): can choose position of special fields --- docs/actions/bazarliste.yaml | 4 ++ .../javascripts/components/BazarTable.js | 62 +++++++++++++++---- .../index-dynamic-templates/table.twig | 1 + 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/docs/actions/bazarliste.yaml b/docs/actions/bazarliste.yaml index bc442169c..d281498e6 100644 --- a/docs/actions/bazarliste.yaml +++ b/docs/actions/bazarliste.yaml @@ -811,6 +811,10 @@ actions: hint: _t(AB_bazartableau_columnfieldsids_hint) multiple: true default: '' + extraFields: + - id_typeannonce + - date_creation_fiche + - date_maj_fiche columntitles: type: text label: _t(AB_bazartableau_columntitles_label) diff --git a/tools/bazar/presentation/javascripts/components/BazarTable.js b/tools/bazar/presentation/javascripts/components/BazarTable.js index 12e35e2fa..0197ddcc5 100644 --- a/tools/bazar/presentation/javascripts/components/BazarTable.js +++ b/tools/bazar/presentation/javascripts/components/BazarTable.js @@ -170,6 +170,7 @@ let componentParams = { displayvaluesinsteadofkeys:this.sanitizedParam(params,this.isAdmin,'displayvaluesinsteadofkeys'), baseIdx: data.columns.length } + let fieldsToRegister = ['date_creation_fiche','date_maj_fiche','owner','id_typeannonce'] columnfieldsids.forEach((id,idx)=>{ if (id.length >0 && id in fields){ this.registerField(data,{ @@ -181,6 +182,9 @@ let componentParams = { addLink:idx === 0 && !('bf_titre' in columnfieldsids) } }) + } else if (fieldsToRegister.includes(id)) { + fieldsToRegister = fieldsToRegister.filter((e)=>e!=id) + this.registerSpecialFields([id],false,params,data) } }) if (await this.sanitizedParamAsync('exportallcolumns')){ @@ -200,19 +204,8 @@ let componentParams = { }) } - [ - ['displaycreationdate','date_creation_fiche','creationdatetranslate'], - ['displaylastchangedate','date_maj_fiche','modifiydatetranslate'], - ['displayowner','owner','ownertranslate'] - ].forEach(([paramName,propertyName,slotName])=>{ - if (this.sanitizedParam(params,this.isadmin,paramName)){ - data.columns.push({ - data: propertyName, - title: this.getTemplateFromSlot(slotName,{}), - footer: '' - }) - } - }) + this.registerSpecialFields(fieldsToRegister,true,params,data) + this.columns = data.columns } return this.columns @@ -471,6 +464,49 @@ let componentParams = { } } }, + registerSpecialFields(fieldsToRegister,test,params,data){ + if (Array.isArray(fieldsToRegister) && fieldsToRegister.length > 0){ + const parameters = { + 'date_creation_fiche': { + paramName: 'displaycreationdate', + slotName: 'creationdatetranslate' + }, + 'date_maj_fiche': { + paramName: 'displaylastchangedate', + slotName: 'modifiydatetranslate' + }, + 'owner': { + paramName: 'displayowner', + slotName: 'ownertranslate' + }, + 'id_typeannonce': { + paramName: '', + slotName: 'formidtranslate' + }, + } + fieldsToRegister.forEach((propertyName)=>{ + if (propertyName in parameters){ + const canPushColumn = + (parameters[propertyName].slotName.length > 0) + ? ( + test + ? ( + parameters[propertyName].paramName.length > 0 && + this.sanitizedParam(params,this.isadmin,parameters[propertyName].paramName) + ) + : true + ) : false + if (canPushColumn){ + data.columns.push({ + data: propertyName, + title: this.getTemplateFromSlot(parameters[propertyName].slotName,{}), + footer: '' + }) + } + } + }) + } + }, removeRows(dataTable,newIds){ let entryIdsToRemove = Object.keys(this.displayedEntries).filter((id)=>!newIds.includes(id)) entryIdsToRemove.forEach((id)=>{ diff --git a/tools/bazar/templates/entries/index-dynamic-templates/table.twig b/tools/bazar/templates/entries/index-dynamic-templates/table.twig index 4d9f04d12..c8cb5991b 100644 --- a/tools/bazar/templates/entries/index-dynamic-templates/table.twig +++ b/tools/bazar/templates/entries/index-dynamic-templates/table.twig @@ -103,6 +103,7 @@ + From 366f11940036d152f4bbfddc8241f5ec71f03d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Thu, 11 May 2023 12:39:03 +0200 Subject: [PATCH 050/152] feat(BazarTable): can choose special field title --- .../presentation/javascripts/components/BazarTable.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/bazar/presentation/javascripts/components/BazarTable.js b/tools/bazar/presentation/javascripts/components/BazarTable.js index 0197ddcc5..094a4ee83 100644 --- a/tools/bazar/presentation/javascripts/components/BazarTable.js +++ b/tools/bazar/presentation/javascripts/components/BazarTable.js @@ -184,7 +184,7 @@ let componentParams = { }) } else if (fieldsToRegister.includes(id)) { fieldsToRegister = fieldsToRegister.filter((e)=>e!=id) - this.registerSpecialFields([id],false,params,data) + this.registerSpecialFields([id],false,params,data,options) } }) if (await this.sanitizedParamAsync('exportallcolumns')){ @@ -204,7 +204,7 @@ let componentParams = { }) } - this.registerSpecialFields(fieldsToRegister,true,params,data) + this.registerSpecialFields(fieldsToRegister,true,params,data,options) this.columns = data.columns } @@ -464,7 +464,7 @@ let componentParams = { } } }, - registerSpecialFields(fieldsToRegister,test,params,data){ + registerSpecialFields(fieldsToRegister,test,params,data,options){ if (Array.isArray(fieldsToRegister) && fieldsToRegister.length > 0){ const parameters = { 'date_creation_fiche': { @@ -499,7 +499,7 @@ let componentParams = { if (canPushColumn){ data.columns.push({ data: propertyName, - title: this.getTemplateFromSlot(parameters[propertyName].slotName,{}), + title: options.columntitles[propertyName] || this.getTemplateFromSlot(parameters[propertyName].slotName,{}), footer: '' }) } From ac4bedfecffa64b0394069dfe40ab8d64dbfdf73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Fri, 12 May 2023 16:15:12 +0200 Subject: [PATCH 051/152] feat(BazarTable): add link to linked Entries --- .../javascripts/components/BazarTable.js | 44 ++++++++++++++++++- .../index-dynamic-templates/table.twig | 6 +++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/tools/bazar/presentation/javascripts/components/BazarTable.js b/tools/bazar/presentation/javascripts/components/BazarTable.js index 094a4ee83..4a100d9a5 100644 --- a/tools/bazar/presentation/javascripts/components/BazarTable.js +++ b/tools/bazar/presentation/javascripts/components/BazarTable.js @@ -55,10 +55,21 @@ let componentParams = { } else if ('displayValOptions' in col){ formattedData[col.data] = (col.data in entry && typeof entry[col.data] === 'string') ? { display:entry[col.data].split(',').map((v)=>v in col.displayValOptions ? col.displayValOptions[v] : v).join(",\n"), - export: entry[col.data].split(',').map((v)=>v in col.displayValOptions ? `"${col.displayValOptions[v]}"` : v).join(',') + export: entry[col.data].split(',').map((v)=>v in col.displayValOptions ? `"${col.displayValOptions[v]}"` : v).join(','), + raw: entry[col.data].split(',').map((v)=>{return {key:v,title:v in col.displayValOptions ? col.displayValOptions[v] : v}}) } : '' + if (formattedData[col.data] !== '' && 'externalBaseUrl' in col){ + formattedData[col.data].externalBaseUrl = col.externalBaseUrl + } } else { formattedData[col.data] = (col.data in entry && typeof entry[col.data] === 'string' ) ? entry[col.data] : '' + if (formattedData[col.data] !== '' && 'externalBaseUrl' in col){ + formattedData[col.data] = { + display:formattedData[col.data], + export:formattedData[col.data], + externalBaseUrl:col.externalBaseUrl + } + } } }); ['id_fiche','color','icon','url'].forEach((key)=>{ @@ -442,13 +453,18 @@ let componentParams = { }) }) } else { - const fieldtype = ['link','email'].includes(field.type) ? field.type: ((field.type === 'image' && displayimagesasthumbnails)?'image':'') + const fieldtype = ['link','email','checkboxfiche','listefiche','radiofiche'].includes(field.type) ? field.type: ((field.type === 'image' && displayimagesasthumbnails)?'image':'') const fieldName = (fieldtype === 'image' && displayimagesasthumbnails) ? field.propertyname : '' const displayValOptions = (displayvaluesinsteadofkeys && 'options' in field && typeof field.options === 'object') ? { displayValOptions:field.options } : {} + if ('linkedObjectName' in field && field.linkedObjectName.match(/^https?:\/\//)){ + displayValOptions.externalBaseUrl = field.linkedObjectName.match(/^(https?:\/\/.*)api\/(forms|entries).*$/) + ? field.linkedObjectName.replace(/^(https?:\/\/.*)api\/(forms|entries).*$/,"$1") + : '' + } data.columns.push({ ...{ class: className, @@ -577,6 +593,30 @@ let componentParams = { } else { anchorData = '' } + } else if (fieldtype === 'checkboxfiche' && typeof data === 'object' && data !== null && 'raw' in data) { + return data.raw.map(({key,title})=>this.renderCell({fieldtype:'urlmodal',fieldName,idx})({display:title},type,{id_fiche:key,url:wiki.url(`${key}/iframe`)})).join(',
') + } else if (typeof fieldtype === 'string' && ['listefiche','radiofiche','checkboxfiche'].includes(fieldtype) && formattedData.length > 0) { + const intFieldType = (typeof data === 'object' && data !== null && 'externalBaseUrl' in data) + ? ( + data.externalBaseUrl.length > 0 + ? 'urlnewwindow' + : '' + ): 'urlmodal' + const formattedArray = (typeof data === 'object' && data !== null && 'raw' in data) + ? data.raw + : formattedData.split(',').map((key)=>{return {key,title:key}}) + return formattedArray.map(({key,title})=>{ + return this.renderCell({fieldtype:intFieldType,fieldName,idx})({display:title},type,{ + id_fiche:key, + url:(intFieldType === 'urlmodal') + ? wiki.url(`${key}/iframe`) + :( + intFieldType === 'urlnewwindow' + ? data.externalBaseUrl + key + : '' + ) + }).trim() + }).join(',\n') } const template = this.getTemplateFromSlot('rendercell',{ anchorData, diff --git a/tools/bazar/templates/entries/index-dynamic-templates/table.twig b/tools/bazar/templates/entries/index-dynamic-templates/table.twig index c8cb5991b..25687175c 100644 --- a/tools/bazar/templates/entries/index-dynamic-templates/table.twig +++ b/tools/bazar/templates/entries/index-dynamic-templates/table.twig @@ -135,6 +135,12 @@ :src="urlImage({id_fiche:entryId,url:wiki.url(entryId),[fieldName]:anchorData},fieldName,{{ imWidth }},{{ imHeight }},'crop')" :onError="`urlImageResizedOnError({id_fiche:'${entryId}',url:'${wiki.url(entryId)}',['${fieldName}']:'${anchorData}'},'${fieldName}',{{ imWidth }},{{ imHeight }},'crop','{{ firstTokenCrop|e('html') }}')`"/> + + From 9f95c2d661f04c6599565fb2382c816b37163b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Fri, 12 May 2023 17:02:06 +0200 Subject: [PATCH 052/152] fix(BazarTable): some troubles with title and row --- .../javascripts/components/BazarTable.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tools/bazar/presentation/javascripts/components/BazarTable.js b/tools/bazar/presentation/javascripts/components/BazarTable.js index 4a100d9a5..6c86d27cd 100644 --- a/tools/bazar/presentation/javascripts/components/BazarTable.js +++ b/tools/bazar/presentation/javascripts/components/BazarTable.js @@ -190,7 +190,7 @@ let componentParams = { field:fields[id], visible:true, printable:true, - addLink:idx === 0 && !('bf_titre' in columnfieldsids) + addLink:(idx === 0 && !columnfieldsids.includes('bf_titre')) || id === 'bf_titre' } }) } else if (fieldsToRegister.includes(id)) { @@ -593,8 +593,6 @@ let componentParams = { } else { anchorData = '' } - } else if (fieldtype === 'checkboxfiche' && typeof data === 'object' && data !== null && 'raw' in data) { - return data.raw.map(({key,title})=>this.renderCell({fieldtype:'urlmodal',fieldName,idx})({display:title},type,{id_fiche:key,url:wiki.url(`${key}/iframe`)})).join(',
') } else if (typeof fieldtype === 'string' && ['listefiche','radiofiche','checkboxfiche'].includes(fieldtype) && formattedData.length > 0) { const intFieldType = (typeof data === 'object' && data !== null && 'externalBaseUrl' in data) ? ( @@ -607,14 +605,17 @@ let componentParams = { : formattedData.split(',').map((key)=>{return {key,title:key}}) return formattedArray.map(({key,title})=>{ return this.renderCell({fieldtype:intFieldType,fieldName,idx})({display:title},type,{ - id_fiche:key, - url:(intFieldType === 'urlmodal') - ? wiki.url(`${key}/iframe`) - :( - intFieldType === 'urlnewwindow' - ? data.externalBaseUrl + key - : '' - ) + ...row, + ...{ + id_fiche:key, + url:(intFieldType === 'urlmodal') + ? wiki.url(`${key}/iframe`) + :( + intFieldType === 'urlnewwindow' + ? data.externalBaseUrl + key + : '' + ) + } }).trim() }).join(',\n') } From 78ebde389b4d20709b765d0b4d347fb0e6a6c259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Wed, 31 May 2023 19:56:17 +0200 Subject: [PATCH 053/152] feat(tabdyn): allow fastsearch --- .../javascripts/components/BazarTable.js | 22 ++++++++++++++++++- .../index-dynamic-templates/table.twig | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tools/bazar/presentation/javascripts/components/BazarTable.js b/tools/bazar/presentation/javascripts/components/BazarTable.js index 6c86d27cd..9338c6076 100644 --- a/tools/bazar/presentation/javascripts/components/BazarTable.js +++ b/tools/bazar/presentation/javascripts/components/BazarTable.js @@ -12,6 +12,7 @@ let componentParams = { columns: [], dataTable: null, displayedEntries: {}, + fastSearch: false, fields: {}, forms: {}, isReady:{ @@ -258,7 +259,8 @@ let componentParams = { return { ...DATATABLE_OPTIONS, ...{ - searching: false, + searching: true,// allow search but ue dom option not display filter + dom:'lrtip', // instead of default lfrtip , with f for filter, see help : https://datatables.net/reference/option/dom footerCallback: ()=>{ this.updateFooter() }, @@ -534,6 +536,14 @@ let componentParams = { return !('id_fiche' in data) || entryIdsToRemove.includes(data.id_fiche) }).remove() }, + resetFastSearch(isOk){ + if (isOk){ + this.fastSearch = false + } + if (this.dataTable !== null){ + this.$nextTick(()=>{this.dataTable.search(this.$root.search).draw()}) + } + }, resolve(name){ this.isReady[name] = true if (name in this.cacheResolveReject && @@ -744,6 +754,12 @@ let componentParams = { this.addRows(dataTable,columns,newEntries,currentusername,this.isadmin) this.dataTable.draw() }, + updateFastSearch(newSearch){ + if (this.dataTable !== null){ + this.dataTable.search(newSearch).draw() + this.fastSearch = true + } + }, updateFieldsFromRoot(){ this.fields = this.$root.formFields if (Object.keys(this.fields).length > 0){ @@ -786,6 +802,10 @@ let componentParams = { }); this.updateFieldsFromRoot() window.urlImageResizedOnError = this.$root.urlImageResizedOnError + this.$root.$watch('search',(newSearch)=>{this.updateFastSearch(newSearch)}) + this.$root.$watch('ready',(newVal)=>{this.resetFastSearch(newVal)}) + this.$root.$watch('isLoading',(newVal)=>{this.resetFastSearch(!newVal)}) + this.$root.$watch('searchedEntries',()=>{this.resetFastSearch(true)}) }, watch: { entries(newVal, oldVal) { diff --git a/tools/bazar/templates/entries/index-dynamic-templates/table.twig b/tools/bazar/templates/entries/index-dynamic-templates/table.twig index 25687175c..eac8dd215 100644 --- a/tools/bazar/templates/entries/index-dynamic-templates/table.twig +++ b/tools/bazar/templates/entries/index-dynamic-templates/table.twig @@ -32,7 +32,7 @@
diff --git a/tools/bazar/templates/entries/index-dynamic-templates/card.twig b/tools/bazar/templates/entries/index-dynamic-templates/card.twig index f93eff592..eb9c44f49 100644 --- a/tools/bazar/templates/entries/index-dynamic-templates/card.twig +++ b/tools/bazar/templates/entries/index-dynamic-templates/card.twig @@ -15,7 +15,7 @@ {% endif %} {% set imWidth = (nbcol == 4) ? 250 : (nbcol == 5 ? 200 : 300 ) %} {% set imHeight = params.style == "square" ? imWidth : (params.style == "horizontal" ? imWidth*23//27 : imWidth*2//3 ) %} - {% set firstTokenCrop = csrfToken("GET api/images/cache/#{imWidth}/#{imHeight}/crop") %} + {% set firstTokenCrop = csrfToken("POST api/images/cache/#{imWidth}/#{imHeight}/crop") %}
diff --git a/tools/bazar/templates/entries/index-dynamic-templates/list.twig b/tools/bazar/templates/entries/index-dynamic-templates/list.twig index be0f1aa3c..ebab279ee 100644 --- a/tools/bazar/templates/entries/index-dynamic-templates/list.twig +++ b/tools/bazar/templates/entries/index-dynamic-templates/list.twig @@ -3,7 +3,7 @@ {% block display_entries %} {% set imWidth = 170 %} {% set imHeight = 170 %} - {% set firstTokenCrop = csrfToken("GET api/images/cache/#{imWidth}/#{imHeight}/crop") %} + {% set firstTokenCrop = csrfToken("POST api/images/cache/#{imWidth}/#{imHeight}/crop") %}
{{ _t('BAZ_NO_RESULT') }} diff --git a/tools/bazar/templates/entries/index-dynamic-templates/table.twig b/tools/bazar/templates/entries/index-dynamic-templates/table.twig index eac8dd215..01dbb7169 100644 --- a/tools/bazar/templates/entries/index-dynamic-templates/table.twig +++ b/tools/bazar/templates/entries/index-dynamic-templates/table.twig @@ -28,7 +28,7 @@ {% block display_entries %} {% set imWidth = 150 %} {% set imHeight = 100 %} - {% set firstTokenCrop = csrfToken("GET api/images/cache/#{imWidth}/#{imHeight}/crop") %} + {% set firstTokenCrop = csrfToken("POST api/images/cache/#{imWidth}/#{imHeight}/crop") %} \ No newline at end of file From 5022ead5a4d502d27b897b548ea8da412479427f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Wed, 26 Jul 2023 10:35:36 +0200 Subject: [PATCH 108/152] fix(PopupEntryField): manage link field --- .../presentation/javascripts/components/PopupEntryField.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/bazar/presentation/javascripts/components/PopupEntryField.js b/tools/bazar/presentation/javascripts/components/PopupEntryField.js index 7bc8cff0c..76b39ff71 100644 --- a/tools/bazar/presentation/javascripts/components/PopupEntryField.js +++ b/tools/bazar/presentation/javascripts/components/PopupEntryField.js @@ -44,6 +44,8 @@ export default { return values.length == 0 ? '' : (values.length == 1 ? values[0] : values) case 'email': return '' // security + case 'link': + return value ? `${value}`: '' default: return value } From d4554b0b903633ec26817f281d8e53630d2e8a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Thu, 27 Jul 2023 15:08:23 +0200 Subject: [PATCH 109/152] refactor(DateField): move include javascript to twig --- tools/bazar/fields/DateField.php | 2 -- tools/bazar/templates/inputs/date.twig | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/bazar/fields/DateField.php b/tools/bazar/fields/DateField.php index cc6e28895..7ed68b974 100644 --- a/tools/bazar/fields/DateField.php +++ b/tools/bazar/fields/DateField.php @@ -9,8 +9,6 @@ class DateField extends BazarField { protected function renderInput($entry) { - $GLOBALS['wiki']->addJavascriptFile('tools/bazar/libs/vendor/bootstrap-datepicker.js'); - $day = ""; $hour = 0; $minute = 0; diff --git a/tools/bazar/templates/inputs/date.twig b/tools/bazar/templates/inputs/date.twig index e5837291b..9ac7577db 100644 --- a/tools/bazar/templates/inputs/date.twig +++ b/tools/bazar/templates/inputs/date.twig @@ -1,6 +1,7 @@ {% extends "@bazar/layouts/input.twig" %} {% block input %} + {{ include_javascript('tools/bazar/libs/vendor/bootstrap-datepicker.js') }}
From 135f3da4faa3ab754176925884aed082e9ab749f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Mon, 31 Jul 2023 12:31:17 +0200 Subject: [PATCH 110/152] fix(bazar.js): check pattern --- tools/bazar/lang/bazarjs_en.inc.php | 1 + tools/bazar/lang/bazarjs_fr.inc.php | 1 + tools/bazar/libs/bazar.js | 47 ++++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/tools/bazar/lang/bazarjs_en.inc.php b/tools/bazar/lang/bazarjs_en.inc.php index 1f1502d16..876b0d7c5 100644 --- a/tools/bazar/lang/bazarjs_en.inc.php +++ b/tools/bazar/lang/bazarjs_en.inc.php @@ -57,6 +57,7 @@ // libs/bazar.js // 'BAZ_FORM_REQUIRED_FIELD' => 'Veuillez saisir tous les champs obligatoires (avec une asterisque rouge)', // 'BAZ_FORM_INVALID_EMAIL' => 'L\'email saisi n\'est pas valide', + // 'BAZ_FORM_INVALID_TEXT' => 'Le texte n\'est pas valide', // 'BAZ_FORM_INVALID_URL' => 'L\'url saisie n\'est pas valide, elle doit commencer par http:// '. // 'et ne pas contenir d\'espaces ou caracteres speciaux', // 'BAZ_FORM_EMPTY_RADIO' => 'Il faut choisir une valeur de bouton radio', diff --git a/tools/bazar/lang/bazarjs_fr.inc.php b/tools/bazar/lang/bazarjs_fr.inc.php index 5667d260e..78acedc79 100644 --- a/tools/bazar/lang/bazarjs_fr.inc.php +++ b/tools/bazar/lang/bazarjs_fr.inc.php @@ -58,6 +58,7 @@ // libs/bazar.js 'BAZ_FORM_REQUIRED_FIELD' => 'Veuillez saisir tous les champs obligatoires (asterisque rouge)', 'BAZ_FORM_INVALID_EMAIL' => 'L\'email saisi n\'est pas valide', + 'BAZ_FORM_INVALID_TEXT' => 'Le texte n\'est pas valide', 'BAZ_FORM_INVALID_URL' => 'L\'url saisie n\'est pas valide, elle doit commencer par https:// '. 'et ne pas contenir d\'espaces ou caracteres speciaux', 'BAZ_FORM_EMPTY_RADIO' => 'Il faut choisir une valeur de bouton radio', diff --git a/tools/bazar/libs/bazar.js b/tools/bazar/libs/bazar.js index cf72b53c6..7f1548706 100755 --- a/tools/bazar/libs/bazar.js +++ b/tools/bazar/libs/bazar.js @@ -181,10 +181,13 @@ $(document).ready(() => { // validation formulaire de saisie const requirementHelper = { requiredInputs: [], + textInputsWithPattern: [], error: -1, // error contain the index of the first error (-1 = no error) errorMessage: '', - filterVisibleInputs() { - this.requiredInputs = this.requiredInputs.filter(function() { + errorPattern: -1, + errorMessagePattern: '', + filterVisibleInputs(key = 'requiredInputs') { + this[key] = (this[key]).filter(function() { let inputVisible = $(this).filter(':visible') if (( $(this).prop('tagName') == 'TEXTAREA' && ($(this).hasClass('wiki-textarea') || $(this).hasClass('summernote')) @@ -381,20 +384,39 @@ $(document).ready(() => { $(input).removeClass('invalid') } }, + checkPattern(input,index){ + if ($(input)[0]){ + const val = $(input).val() + const element = $(input)[0] + if (val.length > 0){ + if (!element.checkValidity()){ + if (this.errorPattern == -1) { + this.errorMessagePattern = _t('BAZ_FORM_INVALID_TEXT') + this.errorPattern= index + } + } + } + } + }, checkInputs() { for (let index = 0; index < this.requiredInputs.length; index++) { const input = this.requiredInputs[index] this.checkInput(input, true, index) } + for (let index = 0; index < this.textInputsWithPattern.length; index++) { + const input = this.textInputsWithPattern[index] + this.checkPattern(input, index) + } }, displayErrorMessage() { alert(this.errorMessage) }, - scrollToFirstinputInError() { - if (this.error > -1) { + scrollToFirstinputInError(type = 'error') { + const error = this[type] ?? -1 + if (error > -1) { // TODO afficher l'onglet en question // on remonte en haut du formulaire - let input = this.requiredInputs[this.error] + let input = this.requiredInputs[error] if ($(input).filter(':visible').length == 0) { // panel ? const panel = $(input).parentsUntil(':visible').last() @@ -408,6 +430,10 @@ $(document).ready(() => { $('html, body').animate({ scrollTop: $(input).offset().top - 80 }, 500) } }, + initTextInputsWithPattern(form) { + this.textInputsWithPattern = $(form).find('input[type=text][pattern]') + this.errorPattern = -1 + }, initRequiredInputs(form) { this.requiredInputs = $(form).find( 'input[required],' @@ -423,11 +449,18 @@ $(document).ready(() => { }, run(form) { this.initRequiredInputs(form) - this.filterVisibleInputs() + this.initTextInputsWithPattern(form) + this.filterVisibleInputs('requiredInputs') + this.filterVisibleInputs('textInputsWithPattern') this.checkInputs() if (this.error > -1) { this.displayErrorMessage() - this.scrollToFirstinputInError() + this.scrollToFirstinputInError('error') + return false + } + if (this.errorPattern > -1) { + alert(this.errorMessagePattern) + this.scrollToFirstinputInError('errorPattern') return false } return true From 6cb556c1fec30beabe3bd0df97c6cfce8758f6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Wed, 2 Aug 2023 13:05:27 +0200 Subject: [PATCH 111/152] fix(list.twig): keep html instead of raw --- tools/bazar/templates/entries/index-dynamic-templates/list.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/bazar/templates/entries/index-dynamic-templates/list.twig b/tools/bazar/templates/entries/index-dynamic-templates/list.twig index ebab279ee..3a57ac304 100644 --- a/tools/bazar/templates/entries/index-dynamic-templates/list.twig +++ b/tools/bazar/templates/entries/index-dynamic-templates/list.twig @@ -24,7 +24,7 @@

- {{"{{ entry.title || entry.bf_titre }}"}} +

{# FLOATING AREA #} From 2fbde7f9a826c15f9103ac7f34ddc2d45680e87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Thu, 3 Aug 2023 12:28:06 +0200 Subject: [PATCH 112/152] fix(bazar-list-dynamic): render html, to not remove fields without id fix #1037 --- tools/bazar/presentation/javascripts/bazar-list-dynamic.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/bazar/presentation/javascripts/bazar-list-dynamic.js b/tools/bazar/presentation/javascripts/bazar-list-dynamic.js index d538c3acc..9bcdf615f 100644 --- a/tools/bazar/presentation/javascripts/bazar-list-dynamic.js +++ b/tools/bazar/presentation/javascripts/bazar-list-dynamic.js @@ -194,8 +194,8 @@ document.addEventListener('DOMContentLoaded', () => { fieldsToExclude = Object.values(this.params.displayfields) } const url = wiki.url(`?api/entries/html/${entry.id_fiche}`, { - fields: 'html_output', - excludeFields: fieldsToExclude + ...{fields: 'html_output'}, + ...(fieldsToExclude.length > 0 ? {excludeFields: fieldsToExclude} :{}) }) $.getJSON(url, (data) => { Vue.set(entry, 'html_render', (data[entry.id_fiche] && data[entry.id_fiche].html_output) ? data[entry.id_fiche].html_output : 'error') From a12d5d3006d9f20adf4f5b629bf1760a3adf5b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Thu, 3 Aug 2023 14:24:35 +0200 Subject: [PATCH 113/152] feat(LinkedEntryField): append title --- tools/bazar/fields/LinkedEntryField.php | 17 ++++++++++++-- .../fields/listefichesliees.js | 22 +++++++++++-------- .../styles/form-edit-template.css | 7 ------ .../bazar/templates/fields/linked-entry.twig | 11 ++++++++++ .../bazar/templates/inputs/linked-entry.twig | 14 ++++++++++++ 5 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 tools/bazar/templates/fields/linked-entry.twig create mode 100644 tools/bazar/templates/inputs/linked-entry.twig diff --git a/tools/bazar/fields/LinkedEntryField.php b/tools/bazar/fields/LinkedEntryField.php index 543134c09..35557f51a 100644 --- a/tools/bazar/fields/LinkedEntryField.php +++ b/tools/bazar/fields/LinkedEntryField.php @@ -25,11 +25,13 @@ class LinkedEntryField extends BazarField protected const FIELD_LIMIT = 4; protected const FIELD_TEMPLATE = 5; protected const FIELD_LINK_TYPE = 6; + protected const FIELD_LABEL = 7; public function __construct(array $values, ContainerInterface $services) { parent::__construct($values, $services); + $this->label = $values[self::FIELD_LABEL] ?? ''; $this->query = $values[self::FIELD_QUERY] ?? ''; $this->otherParams = $values[self::FIELD_OTHER_PARAMS] ?? ''; $this->limit = $values[self::FIELD_LIMIT] ?? ''; @@ -43,7 +45,10 @@ protected function renderInput($entry) { // Display the linked entries only on update if (isset($entry['id_fiche'])) { - return $this->renderSecuredBazarList($entry); + $output = $this->renderSecuredBazarList($entry); + return $this->isEmptyOutput($output) + ? $output + : $this->render('@bazar/inputs/linked-entry.twig',compact(['output'])); } } @@ -51,7 +56,10 @@ protected function renderStatic($entry) { // Display the linked entries only if id_fiche and id_typeannonce if (!empty($entry['id_fiche']) && !empty($entry['id_typeannonce'])) { - return $this->renderSecuredBazarList($entry); + $output = $this->renderSecuredBazarList($entry); + return $this->isEmptyOutput($output) + ? $output + : $this->render('@bazar/fields/linked-entry.twig',compact(['output'])); } else { return "" ; } @@ -66,6 +74,11 @@ protected function renderSecuredBazarList($entry): string return $output; } + protected function isEmptyOutput(string $output): bool + { + return empty($output) || preg_match('/
]*>\s*
<\/div><\/div>/',$output); + } + private function getBazarListAction($entry) { $query = $this->getQueryForLinkedLabels($entry) ; diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js index 0f1c9b1e8..043a9b3e9 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/listefichesliees.js @@ -1,4 +1,4 @@ -import { readConf, writeconf, semanticConf } from './commons/attributes.js' +import { defaultMapping, readConf, writeconf, semanticConf } from './commons/attributes.js' export default { field: { @@ -37,15 +37,19 @@ export default { semantic: semanticConf }, advancedAttributes: ['read', 'write', 'semantic', 'template', 'type_link', 'param','query'], - // disabledAttributes: [], + disabledAttributes: ['required', 'value', 'name'], attributesMapping: { - 0: 'type', - 1: 'id', - 2: 'query', - 3: 'param', - 4: 'number', - 5: 'template', - 6: 'type_link' + ...defaultMapping, + ...{ + 0: 'type', + 1: 'id', + 2: 'query', + 3: 'param', + 4: 'number', + 5: 'template', + 6: 'type_link', + 7: 'label' + } }, renderInput(field) { return { field: '' } diff --git a/tools/bazar/presentation/styles/form-edit-template.css b/tools/bazar/presentation/styles/form-edit-template.css index d7ced4eab..af63c03df 100644 --- a/tools/bazar/presentation/styles/form-edit-template.css +++ b/tools/bazar/presentation/styles/form-edit-template.css @@ -182,13 +182,6 @@ display: none !important; } -.listefichesliees-field .required-wrap, -.listefichesliees-field .name-wrap, -.listefichesliees-field .value-wrap, -.listefichesliees-field .label-wrap { - display: none !important; -} - .bookmarklet-field .required-wrap { display: none !important; } diff --git a/tools/bazar/templates/fields/linked-entry.twig b/tools/bazar/templates/fields/linked-entry.twig new file mode 100644 index 000000000..79e7c2d18 --- /dev/null +++ b/tools/bazar/templates/fields/linked-entry.twig @@ -0,0 +1,11 @@ +{% if field.label is not empty %} + {% embed "@bazar/layouts/field.twig" %} + {% block value_container %} + {{ output|raw }} + {% endblock %} + {% endembed %} +{% else %} + {{ output|raw }} +{% endif %} + + diff --git a/tools/bazar/templates/inputs/linked-entry.twig b/tools/bazar/templates/inputs/linked-entry.twig new file mode 100644 index 000000000..9c94dc5cc --- /dev/null +++ b/tools/bazar/templates/inputs/linked-entry.twig @@ -0,0 +1,14 @@ +{% if field.label is not empty %} + {% embed "@bazar/layouts/input.twig" %} + {% block label %} + {{ field.label|raw }} + {% endblock %} + {% block input %} + {{ output|raw }} + {% endblock %} + {% endembed %} +{% else %} + {{ output|raw }} +{% endif %} + + From b4408fadd7b0b55072e866f28669b8baf9947cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Fri, 4 Aug 2023 11:53:04 +0200 Subject: [PATCH 114/152] fix(fields/map.js): remove previous system for advanced params --- .../form-edit-template/fields/map.js | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js b/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js index 67e9812a7..a2d08581a 100644 --- a/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js +++ b/tools/bazar/presentation/javascripts/form-edit-template/fields/map.js @@ -166,28 +166,6 @@ export default {
${_t('GEOLOCATER_GROUP_GEOLOCATIZATION_HINT')}
`) - const $advancedParams = $(` -
- -
- -
-
- `) - renderHelper.prependHTMLBeforeGroup(fieldData, 'autocomplete_street1', $advancedParams) - $advancedParams.find('button').on('click', function(event) { - if ($(this).hasClass('opened')) { - $(this).removeClass('opened') - toggleStates('hide') - } else { - $(this).addClass('opened') - toggleStates('show') - } - event.preventDefault() - event.stopPropagation() - }) - toggleStates('hide') - renderHelper.prependHTMLBeforeGroup(fieldData, 'geolocate', '

') } } } From 05b6b307aa3e359cc74f66c735c6143627d616da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Fri, 4 Aug 2023 11:57:08 +0200 Subject: [PATCH 115/152] refactor(bazar-list-dynamic): use fetch to get entry content --- .../javascripts/bazar-list-dynamic.js | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/tools/bazar/presentation/javascripts/bazar-list-dynamic.js b/tools/bazar/presentation/javascripts/bazar-list-dynamic.js index 9bcdf615f..e57215a0c 100644 --- a/tools/bazar/presentation/javascripts/bazar-list-dynamic.js +++ b/tools/bazar/presentation/javascripts/bazar-list-dynamic.js @@ -197,11 +197,32 @@ document.addEventListener('DOMContentLoaded', () => { ...{fields: 'html_output'}, ...(fieldsToExclude.length > 0 ? {excludeFields: fieldsToExclude} :{}) }) - $.getJSON(url, (data) => { - Vue.set(entry, 'html_render', (data[entry.id_fiche] && data[entry.id_fiche].html_output) ? data[entry.id_fiche].html_output : 'error') - }) + this.setEntryFromUrl(entry,url) } }, + async setEntryFromUrl(entry,url){ + return await this.getJSON(url) + .then((data)=>{ + const html = data?.[entry.id_fiche]?.html_output ?? 'error' + Vue.set(entry, 'html_render',html) + return html + }).catch(()=>'error')// in case of error do nothing + }, + async getJSON(url,options={}){ + return await fetch(url,options) + .then((response)=>{ + if (!response.ok){ + throw `response not ok ; code : ${response.status} (${response.statusText})` + } + return response.json() + }) + .catch((error)=>{ + if (wiki?.isDebugEnabled){ + console.error(error) + } + return {} + }) + }, fieldInfo(field) { return this.formFields[field] || {} }, From 773ddce3771f3268680a326bab922be2c2b05a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Fri, 4 Aug 2023 12:01:17 +0200 Subject: [PATCH 116/152] refactor(bazar-list-dynamic): define function load --- .../javascripts/bazar-list-dynamic.js | 730 +++++++++--------- 1 file changed, 366 insertions(+), 364 deletions(-) diff --git a/tools/bazar/presentation/javascripts/bazar-list-dynamic.js b/tools/bazar/presentation/javascripts/bazar-list-dynamic.js index e57215a0c..a2ee1d18f 100644 --- a/tools/bazar/presentation/javascripts/bazar-list-dynamic.js +++ b/tools/bazar/presentation/javascripts/bazar-list-dynamic.js @@ -5,402 +5,404 @@ import SpinnerLoader from './components/SpinnerLoader.js' import ModalEntry from './components/ModalEntry.js' import BazarSearch from './components/BazarSearch.js' -// Wait for Dom to be loaded, so we can load some Vue component like BazarpMap in order -// to be used inside index-dynamic -document.addEventListener('DOMContentLoaded', () => { - document.querySelectorAll('.bazar-list-dynamic-container').forEach((domElement) => { - new Vue({ - el: domElement, - components: { Panel, ModalEntry, SpinnerLoader, EntryField, PopupEntryField }, - mixins: [BazarSearch], - data: { - mounted: false, // when vue get initialized - ready: false, // when ajax data have been retrieved - params: {}, +const load = (domElement) => { + new Vue({ + el: domElement, + components: { Panel, ModalEntry, SpinnerLoader, EntryField, PopupEntryField }, + mixins: [BazarSearch], + data: { + mounted: false, // when vue get initialized + ready: false, // when ajax data have been retrieved + params: {}, - filters: [], - entries: [], - formFields: {}, - searchedEntries: [], - filteredEntries: [], - paginatedEntries: [], - entriesToDisplay: [], + filters: [], + entries: [], + formFields: {}, + searchedEntries: [], + filteredEntries: [], + paginatedEntries: [], + entriesToDisplay: [], - currentPage: 0, - pagination: 10, - tokenForImages: null, - imagesToProcess: [], - processingImage: false, - search: '', - searchFormId: null, // wether to search for a particular form ID (only used when no form id is defined for the bazar list action) - searchTimer: null // use ot debounce user input + currentPage: 0, + pagination: 10, + tokenForImages: null, + imagesToProcess: [], + processingImage: false, + search: '', + searchFormId: null, // wether to search for a particular form ID (only used when no form id is defined for the bazar list action) + searchTimer: null // use ot debounce user input + }, + computed: { + computedFilters() { + const result = {} + for (const filterId in this.filters) { + const checkedValues = this.filters[filterId].list.filter((option) => option.checked) + .map((option) => option.value) + if (checkedValues.length > 0) result[filterId] = checkedValues + } + return result }, - computed: { - computedFilters() { - const result = {} - for (const filterId in this.filters) { - const checkedValues = this.filters[filterId].list.filter((option) => option.checked) - .map((option) => option.value) - if (checkedValues.length > 0) result[filterId] = checkedValues - } - return result - }, - filteredEntriesCount() { - return this.filteredEntries.length - }, - pages() { - if (this.pagination <= 0) return [] - const pagesCount = Math.ceil(this.filteredEntries.length / parseInt(this.pagination)) - const start = 0; const - end = pagesCount - 1 - let pages = [this.currentPage - 2, this.currentPage - 1, this.currentPage, this.currentPage + 1, this.currentPage + 2] - pages = pages.filter((page) => page >= start && page <= end) - if (!pages.includes(start)) { - if (!pages.includes(start + 1)) pages.unshift('divider') - pages.unshift(0) - } - if (!pages.includes(end)) { - if (!pages.includes(end - 1)) pages.push('divider') - pages.push(end) - } - return pages + filteredEntriesCount() { + return this.filteredEntries.length + }, + pages() { + if (this.pagination <= 0) return [] + const pagesCount = Math.ceil(this.filteredEntries.length / parseInt(this.pagination)) + const start = 0; const + end = pagesCount - 1 + let pages = [this.currentPage - 2, this.currentPage - 1, this.currentPage, this.currentPage + 1, this.currentPage + 2] + pages = pages.filter((page) => page >= start && page <= end) + if (!pages.includes(start)) { + if (!pages.includes(start + 1)) pages.unshift('divider') + pages.unshift(0) + } + if (!pages.includes(end)) { + if (!pages.includes(end - 1)) pages.push('divider') + pages.push(end) } + return pages + } + }, + watch: { + filteredEntriesCount() { this.currentPage = 0 }, + search() { + clearTimeout(this.searchTimer) + this.searchTimer = setTimeout(() => this.calculateBaseEntries(), 350) + this.saveFiltersIntoHash() }, - watch: { - filteredEntriesCount() { this.currentPage = 0 }, - search() { - clearTimeout(this.searchTimer) - this.searchTimer = setTimeout(() => this.calculateBaseEntries(), 350) - this.saveFiltersIntoHash() - }, - searchFormId() { this.calculateBaseEntries() }, - computedFilters() { - this.filterEntries() - this.saveFiltersIntoHash() - }, - currentPage() { this.paginateEntries() }, - searchedEntries() { this.calculateFiltersCount() } + searchFormId() { this.calculateBaseEntries() }, + computedFilters() { + this.filterEntries() + this.saveFiltersIntoHash() }, - methods: { - calculateBaseEntries() { - let result = this.entries - if (this.searchFormId) { - result = result.filter((entry) => - // filter based on formId, when no form id is specified - entry.id_typeannonce == this.searchFormId) - } - if (this.search && this.search.length > 2) { - result = this.searchEntries(result, this.search) - if (result == undefined) { - result = this.entries - } - } - this.searchedEntries = result - this.filterEntries() - }, - filterEntries() { - // Handles filters - let result = this.searchedEntries - for (const filterId in this.computedFilters) { - result = result.filter((entry) => { - if (!entry[filterId] || typeof entry[filterId] != 'string') return false - return entry[filterId].split(',').some((value) => this.computedFilters[filterId].includes(value)) - }) - } - this.filteredEntries = result - this.paginateEntries() - }, - paginateEntries() { - let result = this.filteredEntries - if (this.pagination > 0) { - const start = this.pagination * this.currentPage - result = result.slice(start, start + this.pagination) + currentPage() { this.paginateEntries() }, + searchedEntries() { this.calculateFiltersCount() } + }, + methods: { + calculateBaseEntries() { + let result = this.entries + if (this.searchFormId) { + result = result.filter((entry) => + // filter based on formId, when no form id is specified + entry.id_typeannonce == this.searchFormId) + } + if (this.search && this.search.length > 2) { + result = this.searchEntries(result, this.search) + if (result == undefined) { + result = this.entries } - this.paginatedEntries = result - this.formatEntries() - }, - formatEntries() { - this.paginatedEntries.forEach((entry) => { - entry.color = this.colorIconValueFor(entry, this.params.colorfield, this.params.color) - entry.icon = this.colorIconValueFor(entry, this.params.iconfield, this.params.icon) + } + this.searchedEntries = result + this.filterEntries() + }, + filterEntries() { + // Handles filters + let result = this.searchedEntries + for (const filterId in this.computedFilters) { + result = result.filter((entry) => { + if (!entry[filterId] || typeof entry[filterId] != 'string') return false + return entry[filterId].split(',').some((value) => this.computedFilters[filterId].includes(value)) }) - this.entriesToDisplay = this.paginatedEntries - }, - calculateFiltersCount() { - for (const fieldName in this.filters) { - for (const option of this.filters[fieldName].list) { - option.nb = this.searchedEntries.filter((entry) => { - let entryValues = entry[fieldName] - if (!entryValues || typeof entryValues != 'string') return - entryValues = entryValues.split(',') - return entryValues.some((value) => value == option.value) - }).length - } - } - }, - resetFilters() { - for (const filterId in this.filters) { - this.filters[filterId].list.forEach((option) => option.checked = false) + } + this.filteredEntries = result + this.paginateEntries() + }, + paginateEntries() { + let result = this.filteredEntries + if (this.pagination > 0) { + const start = this.pagination * this.currentPage + result = result.slice(start, start + this.pagination) + } + this.paginatedEntries = result + this.formatEntries() + }, + formatEntries() { + this.paginatedEntries.forEach((entry) => { + entry.color = this.colorIconValueFor(entry, this.params.colorfield, this.params.color) + entry.icon = this.colorIconValueFor(entry, this.params.iconfield, this.params.icon) + }) + this.entriesToDisplay = this.paginatedEntries + }, + calculateFiltersCount() { + for (const fieldName in this.filters) { + for (const option of this.filters[fieldName].list) { + option.nb = this.searchedEntries.filter((entry) => { + let entryValues = entry[fieldName] + if (!entryValues || typeof entryValues != 'string') return + entryValues = entryValues.split(',') + return entryValues.some((value) => value == option.value) + }).length } - this.search = '' - }, - saveFiltersIntoHash() { - if (!this.ready) return - const hashes = [] - for (const filterId in this.computedFilters) { - hashes.push(`${filterId}=${this.computedFilters[filterId].join(',')}`) + } + }, + resetFilters() { + for (const filterId in this.filters) { + this.filters[filterId].list.forEach((option) => option.checked = false) + } + this.search = '' + }, + saveFiltersIntoHash() { + if (!this.ready) return + const hashes = [] + for (const filterId in this.computedFilters) { + hashes.push(`${filterId}=${this.computedFilters[filterId].join(',')}`) + } + if (this.search) hashes.push(`q=${this.search}`) + document.location.hash = hashes.length > 0 ? hashes.join('&') : null + }, + initFiltersFromHash(filters, hash) { + hash = hash.substring(1) // remove # + for (const combinaison of hash.split('&')) { + const filterId = combinaison.split('=')[0] + let filterValues = combinaison.split('=')[1] + if (filterId == 'q') { + this.search = filterValues + } else if (filterId && filterValues && filters[filterId]) { + filterValues = filterValues.split(',') + for (const filter of filters[filterId].list) { + if (filterValues.includes(filter.value)) filter.checked = true + } } - if (this.search) hashes.push(`q=${this.search}`) - document.location.hash = hashes.length > 0 ? hashes.join('&') : null - }, - initFiltersFromHash(filters, hash) { - hash = hash.substring(1) // remove # - for (const combinaison of hash.split('&')) { + } + // init q from GET q also + if (this.search.length == 0) { + let params = document.location.search + params = params.substring(1) // remove ? + for (const combinaison of params.split('&')) { const filterId = combinaison.split('=')[0] - let filterValues = combinaison.split('=')[1] + const filterValues = combinaison.split('=')[1] if (filterId == 'q') { - this.search = filterValues - } else if (filterId && filterValues && filters[filterId]) { - filterValues = filterValues.split(',') - for (const filter of filters[filterId].list) { - if (filterValues.includes(filter.value)) filter.checked = true - } + this.search = decodeURIComponent(filterValues) } } - // init q from GET q also - if (this.search.length == 0) { - let params = document.location.search - params = params.substring(1) // remove ? - for (const combinaison of params.split('&')) { - const filterId = combinaison.split('=')[0] - const filterValues = combinaison.split('=')[1] - if (filterId == 'q') { - this.search = decodeURIComponent(filterValues) + } + return filters + }, + getEntryRender(entry) { + if (entry.html_render) return + if (this.isExternalUrl(entry)) { + this.getExternalEntry(entry) + } else { + let fieldsToExclude = [] + if (this.params.template == 'list' && this.params.displayfields) { + // In list template (collapsible panels with header and body), the rendered entry + // is displayed in the body section and we don't want to show the fields + // that are already displayed in the panel header + fieldsToExclude = Object.values(this.params.displayfields) + } + const url = wiki.url(`?api/entries/html/${entry.id_fiche}`, { + ...{fields: 'html_output'}, + ...(fieldsToExclude.length > 0 ? {excludeFields: fieldsToExclude} :{}) + }) + this.setEntryFromUrl(entry,url) + } + }, + async setEntryFromUrl(entry,url){ + return await this.getJSON(url) + .then((data)=>{ + const html = data?.[entry.id_fiche]?.html_output ?? 'error' + Vue.set(entry, 'html_render',html) + return html + }).catch(()=>'error')// in case of error do nothing + }, + async getJSON(url,options={}){ + return await fetch(url,options) + .then((response)=>{ + if (!response.ok){ + throw `response not ok ; code : ${response.status} (${response.statusText})` } + return response.json() + }) + .catch((error)=>{ + if (wiki?.isDebugEnabled){ + console.error(error) } - } - return filters - }, - getEntryRender(entry) { - if (entry.html_render) return - if (this.isExternalUrl(entry)) { - this.getExternalEntry(entry) - } else { - let fieldsToExclude = [] - if (this.params.template == 'list' && this.params.displayfields) { - // In list template (collapsible panels with header and body), the rendered entry - // is displayed in the body section and we don't want to show the fields - // that are already displayed in the panel header - fieldsToExclude = Object.values(this.params.displayfields) + return {} + }) + }, + fieldInfo(field) { + return this.formFields[field] || {} + }, + openEntry(entry) { + if (this.params.entrydisplay == 'newtab') window.open(entry.url) + else this.$root.openEntryModal(entry) + }, + openEntryModal(entry) { + this.$refs.modal.displayEntry(entry) + }, + isExternalUrl(entry) { + if (!entry.url) { + return false + } + return entry.url !== wiki.url(entry.id_fiche) + }, + isInIframe() { + return (window != window.parent) + }, + getExternalEntry(entry) { + const url = `${entry.url}/iframe` + Vue.set(entry, 'html_render', ``) + }, + colorIconValueFor(entry, field, mapping) { + if (!entry[field] || typeof entry[field] != 'string') return null + let values = entry[field].split(',') + // If some filters are checked, and the entry have multiple values, we display + // the value associated with the checked filter + // TODO BazarListDynamic check with users if this is expected behaviour + if (this.computedFilters[field]) values = values.filter((val) => this.computedFilters[field].includes(val)) + return mapping[values[0]] + }, + urlImageResizedOnError(entry, fieldName, width, height, mode, token) { + const node = event.target + $(node).removeAttr('onerror') + if (entry[fieldName]) { + const fileName = entry[fieldName] + if (!this.isExternalUrl(entry)) { + // currently not supporting api for external images (anti-csrf token not generated) + if (this.tokenForImages === null) { + this.tokenForImages = token } - const url = wiki.url(`?api/entries/html/${entry.id_fiche}`, { - ...{fields: 'html_output'}, - ...(fieldsToExclude.length > 0 ? {excludeFields: fieldsToExclude} :{}) + this.imagesToProcess.push({ + fileName, + width, + height, + mode, + node }) - this.setEntryFromUrl(entry,url) - } - }, - async setEntryFromUrl(entry,url){ - return await this.getJSON(url) - .then((data)=>{ - const html = data?.[entry.id_fiche]?.html_output ?? 'error' - Vue.set(entry, 'html_render',html) - return html - }).catch(()=>'error')// in case of error do nothing - }, - async getJSON(url,options={}){ - return await fetch(url,options) - .then((response)=>{ - if (!response.ok){ - throw `response not ok ; code : ${response.status} (${response.statusText})` - } - return response.json() - }) - .catch((error)=>{ - if (wiki?.isDebugEnabled){ - console.error(error) - } - return {} - }) - }, - fieldInfo(field) { - return this.formFields[field] || {} - }, - openEntry(entry) { - if (this.params.entrydisplay == 'newtab') window.open(entry.url) - else this.$root.openEntryModal(entry) - }, - openEntryModal(entry) { - this.$refs.modal.displayEntry(entry) - }, - isExternalUrl(entry) { - if (!entry.url) { - return false - } - return entry.url !== wiki.url(entry.id_fiche) - }, - isInIframe() { - return (window != window.parent) - }, - getExternalEntry(entry) { - const url = `${entry.url}/iframe` - Vue.set(entry, 'html_render', ``) - }, - colorIconValueFor(entry, field, mapping) { - if (!entry[field] || typeof entry[field] != 'string') return null - let values = entry[field].split(',') - // If some filters are checked, and the entry have multiple values, we display - // the value associated with the checked filter - // TODO BazarListDynamic check with users if this is expected behaviour - if (this.computedFilters[field]) values = values.filter((val) => this.computedFilters[field].includes(val)) - return mapping[values[0]] - }, - urlImageResizedOnError(entry, fieldName, width, height, mode, token) { - const node = event.target - $(node).removeAttr('onerror') - if (entry[fieldName]) { - const fileName = entry[fieldName] - if (!this.isExternalUrl(entry)) { - // currently not supporting api for external images (anti-csrf token not generated) - if (this.tokenForImages === null) { - this.tokenForImages = token - } - this.imagesToProcess.push({ - fileName, - width, - height, - mode, - node + this.processNextImage() + } else { + const baseUrl = entry.url.slice(0, -entry.id_fiche.length).replace(/\?$/, '').replace(/\/$/, '') + const previousUrl = $(node).prop('src') + const newUrl = `${baseUrl}/files/${fileName}` + if (newUrl != previousUrl) { + $(`img[src="${previousUrl}"]`).each(function() { + $(this).prop('src', newUrl) }) - this.processNextImage() - } else { - const baseUrl = entry.url.slice(0, -entry.id_fiche.length).replace(/\?$/, '').replace(/\/$/, '') - const previousUrl = $(node).prop('src') - const newUrl = `${baseUrl}/files/${fileName}` - if (newUrl != previousUrl) { - $(`img[src="${previousUrl}"]`).each(function() { - $(this).prop('src', newUrl) - }) - } } } - }, - urlImage(entry, fieldName, width, height, mode) { - if (!entry[fieldName]) { - return null - } - let baseUrl = (this.isExternalUrl(entry)) - ? entry.url.slice(0, -entry.id_fiche.length) - : wiki.baseUrl - baseUrl = baseUrl.replace(/\?$/, '').replace(/\/$/, '') - const fileName = entry[fieldName] - const field = this.fieldInfo(fieldName) - let regExp = new RegExp(`^(${entry.id_fiche}_${field.propertyname}_.*)_(\\d{14})_(\\d{14})\\.([^.]+)$`) + } + }, + urlImage(entry, fieldName, width, height, mode) { + if (!entry[fieldName]) { + return null + } + let baseUrl = (this.isExternalUrl(entry)) + ? entry.url.slice(0, -entry.id_fiche.length) + : wiki.baseUrl + baseUrl = baseUrl.replace(/\?$/, '').replace(/\/$/, '') + const fileName = entry[fieldName] + const field = this.fieldInfo(fieldName) + let regExp = new RegExp(`^(${entry.id_fiche}_${field.propertyname}_.*)_(\\d{14})_(\\d{14})\\.([^.]+)$`) - if (regExp.test(fileName)) { - return `${baseUrl}/cache/${fileName.replace(regExp, `$1_${mode == 'fit' ? 'vignette' : 'cropped'}_${width}_${height}_$2_$3.$4`)}` - } - regExp = new RegExp(`^(${entry.id_fiche}_${field.propertyname}_.*)\\.([^.]+)$`) - if (regExp.test(fileName)) { - return `${baseUrl}/cache/${fileName.replace(regExp, `$1_${mode == 'fit' ? 'vignette' : 'cropped'}_${width}_${height}.$2`)}` - } - // maybe from other entry - regExp = new RegExp(`^([A-Za-z0-9-_]+_${field.propertyname}_.*)_(\\d{14})_(\\d{14})\\.([^.]+)$`) - if (regExp.test(fileName)) { - return `${baseUrl}/cache/${fileName.replace(regExp, `$1_${mode == 'fit' ? 'vignette' : 'cropped'}_${width}_${height}_$2_$3.$4`)}` - } - // last possible format - regExp = new RegExp('^(.*)\\.([^.]+)$') - if (regExp.test(fileName)) { - return `${baseUrl}/cache/${fileName.replace(regExp, `$1_${mode == 'fit' ? 'vignette' : 'cropped'}_${width}_${height}.$2`)}` - } - return `${baseUrl}/files/${fileName}` - }, - processNextImage() { - if (!this.processingImage && this.imagesToProcess.length > 0) { - this.processingImage = true - const newImageParams = this.imagesToProcess[0] - this.imagesToProcess = this.imagesToProcess.slice(1) - const bazarListDynamicRoot = this - $.ajax({ - url: wiki.url(`?api/images/${newImageParams.fileName}/cache/${newImageParams.width}/${newImageParams.height}/${newImageParams.mode}`), - method: 'post', - data: {csrftoken: this.tokenForImages}, - cache: false, - success(data) { - const previousUrl = $(newImageParams.node).prop('src') - const srcFileName = wiki.baseUrl.replace(/(\?)?$/, '') + data.cachefilename - $(`img[src="${previousUrl}"]`).each(function() { - $(this).prop('src', srcFileName) - const next = $(this).next('div.area.visual-area[style]') - if (next.length > 0) { - const backgoundImage = $(next).css('background-image') - if (backgoundImage != undefined && typeof backgoundImage == 'string' && backgoundImage.length > 0) { - $(next).css('background-image', '') // reset to force update - $(next).css('background-image', `url("${srcFileName}")`) - } + if (regExp.test(fileName)) { + return `${baseUrl}/cache/${fileName.replace(regExp, `$1_${mode == 'fit' ? 'vignette' : 'cropped'}_${width}_${height}_$2_$3.$4`)}` + } + regExp = new RegExp(`^(${entry.id_fiche}_${field.propertyname}_.*)\\.([^.]+)$`) + if (regExp.test(fileName)) { + return `${baseUrl}/cache/${fileName.replace(regExp, `$1_${mode == 'fit' ? 'vignette' : 'cropped'}_${width}_${height}.$2`)}` + } + // maybe from other entry + regExp = new RegExp(`^([A-Za-z0-9-_]+_${field.propertyname}_.*)_(\\d{14})_(\\d{14})\\.([^.]+)$`) + if (regExp.test(fileName)) { + return `${baseUrl}/cache/${fileName.replace(regExp, `$1_${mode == 'fit' ? 'vignette' : 'cropped'}_${width}_${height}_$2_$3.$4`)}` + } + // last possible format + regExp = new RegExp('^(.*)\\.([^.]+)$') + if (regExp.test(fileName)) { + return `${baseUrl}/cache/${fileName.replace(regExp, `$1_${mode == 'fit' ? 'vignette' : 'cropped'}_${width}_${height}.$2`)}` + } + return `${baseUrl}/files/${fileName}` + }, + processNextImage() { + if (!this.processingImage && this.imagesToProcess.length > 0) { + this.processingImage = true + const newImageParams = this.imagesToProcess[0] + this.imagesToProcess = this.imagesToProcess.slice(1) + const bazarListDynamicRoot = this + $.ajax({ + url: wiki.url(`?api/images/${newImageParams.fileName}/cache/${newImageParams.width}/${newImageParams.height}/${newImageParams.mode}`), + method: 'post', + data: {csrftoken: this.tokenForImages}, + cache: false, + success(data) { + const previousUrl = $(newImageParams.node).prop('src') + const srcFileName = wiki.baseUrl.replace(/(\?)?$/, '') + data.cachefilename + $(`img[src="${previousUrl}"]`).each(function() { + $(this).prop('src', srcFileName) + const next = $(this).next('div.area.visual-area[style]') + if (next.length > 0) { + const backgoundImage = $(next).css('background-image') + if (backgoundImage != undefined && typeof backgoundImage == 'string' && backgoundImage.length > 0) { + $(next).css('background-image', '') // reset to force update + $(next).css('background-image', `url("${srcFileName}")`) } - }) - }, - complete(e) { - if (e.responseJSON != undefined && e.responseJSON.newToken != undefined) { - bazarListDynamicRoot.tokenForImages = e.responseJSON.newToken } - bazarListDynamicRoot.processingImage = false - bazarListDynamicRoot.processNextImage() + }) + }, + complete(e) { + if (e.responseJSON != undefined && e.responseJSON.newToken != undefined) { + bazarListDynamicRoot.tokenForImages = e.responseJSON.newToken } - }) - } + bazarListDynamicRoot.processingImage = false + bazarListDynamicRoot.processNextImage() + } + }) } - }, - mounted() { - $(this.$el).on( - 'dblclick', - (e) => false - ) - const savedHash = document.location.hash // don't know how, but the hash get cleared after - this.params = JSON.parse(this.$el.dataset.params) - this.pagination = parseInt(this.params.pagination) - this.mounted = true - // Retrieve data asynchronoulsy - $.getJSON(wiki.url('?api/entries/bazarlist'), this.params, (data) => { - // First display filters cause entries can be a bit long to load - this.filters = this.initFiltersFromHash(data.filters || [], savedHash) + } + }, + mounted() { + $(this.$el).on( + 'dblclick', + (e) => false + ) + const savedHash = document.location.hash // don't know how, but the hash get cleared after + this.params = JSON.parse(this.$el.dataset.params) + this.pagination = parseInt(this.params.pagination) + this.mounted = true + // Retrieve data asynchronoulsy + $.getJSON(wiki.url('?api/entries/bazarlist'), this.params, (data) => { + // First display filters cause entries can be a bit long to load + this.filters = this.initFiltersFromHash(data.filters || [], savedHash) - // Auto adjust some params depending on entries count - if (data.entries.length > 50 && !this.pagination) this.pagination = 20 // Auto paginate if large numbers - if (data.entries.length > 1000) this.params.cluster = true // Activate cluster for map mode + // Auto adjust some params depending on entries count + if (data.entries.length > 50 && !this.pagination) this.pagination = 20 // Auto paginate if large numbers + if (data.entries.length > 1000) this.params.cluster = true // Activate cluster for map mode - setTimeout(() => { - // Transform forms info into a list of field mapping - // { bf_titre: { type: 'text', ...}, bf_date: { type: 'listedatedeb', ... } } - Object.values(data.forms).forEach((formFields) => { - Object.values(formFields).forEach((field) => { - this.formFields[field.id] = field - Object.entries(this.params.displayfields).forEach(([fieldId, mappedField]) => { - if (mappedField == field.id) this.formFields[fieldId] = this.formFields[mappedField] - }) + setTimeout(() => { + // Transform forms info into a list of field mapping + // { bf_titre: { type: 'text', ...}, bf_date: { type: 'listedatedeb', ... } } + Object.values(data.forms).forEach((formFields) => { + Object.values(formFields).forEach((field) => { + this.formFields[field.id] = field + Object.entries(this.params.displayfields).forEach(([fieldId, mappedField]) => { + if (mappedField == field.id) this.formFields[fieldId] = this.formFields[mappedField] }) }) + }) - this.entries = data.entries.map((array) => { - const entry = { color: null, icon: null } - // Transform array data into object using the fieldMapping - for (const key in data.fieldMapping) { - entry[data.fieldMapping[key]] = array[key] - } - Object.entries(this.params.displayfields).forEach(([field, mappedField]) => { - if (mappedField) entry[field] = entry[mappedField] - }) - - return entry + this.entries = data.entries.map((array) => { + const entry = { color: null, icon: null } + // Transform array data into object using the fieldMapping + for (const key in data.fieldMapping) { + entry[data.fieldMapping[key]] = array[key] + } + Object.entries(this.params.displayfields).forEach(([field, mappedField]) => { + if (mappedField) entry[field] = entry[mappedField] }) - this.calculateBaseEntries() - this.ready = true - }, 0) - }) - } - }) + return entry + }) + + this.calculateBaseEntries() + this.ready = true + }, 0) + }) + } }) +} + +// Wait for Dom to be loaded, so we can load some Vue component like BazarpMap in order +// to be used inside index-dynamic +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('.bazar-list-dynamic-container').forEach(load) }) From 66d2ac24f6f881ca40872198e40eb1c602720fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Fri, 4 Aug 2023 12:02:30 +0200 Subject: [PATCH 117/152] fix(bazar-list-dynamic): load sublevel bazar list dynamic if needed --- .../presentation/javascripts/bazar-list-dynamic.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/bazar/presentation/javascripts/bazar-list-dynamic.js b/tools/bazar/presentation/javascripts/bazar-list-dynamic.js index a2ee1d18f..d8c0f4b0b 100644 --- a/tools/bazar/presentation/javascripts/bazar-list-dynamic.js +++ b/tools/bazar/presentation/javascripts/bazar-list-dynamic.js @@ -194,7 +194,7 @@ const load = (domElement) => { ...{fields: 'html_output'}, ...(fieldsToExclude.length > 0 ? {excludeFields: fieldsToExclude} :{}) }) - this.setEntryFromUrl(entry,url) + this.setEntryFromUrl(entry,url).then(this.loadBazarListDynamicIfNeeded) } }, async setEntryFromUrl(entry,url){ @@ -220,6 +220,15 @@ const load = (domElement) => { return {} }) }, + loadBazarListDynamicIfNeeded(html){ + if (html.match(/