diff --git a/system/classes/DbService.php b/system/classes/DbService.php index f212e5dc1..9542d6218 100755 --- a/system/classes/DbService.php +++ b/system/classes/DbService.php @@ -75,6 +75,14 @@ public function __construct(Web $w) $this->w = $w; } + /** + * @return MenuLinkType[] + */ + public function navList(): array + { + return []; + } + /** * Formats a timestamp * per default MySQL datetime format is used diff --git a/system/classes/History.php b/system/classes/History.php index 86222646a..a9e648e1d 100755 --- a/system/classes/History.php +++ b/system/classes/History.php @@ -127,7 +127,7 @@ public static function remove($object = null) */ private static function sort($a, $b) { - return $a['time'] - $b['time']; + return $b['time'] - $a['time']; } /** diff --git a/system/classes/MenuLinkStruct.php b/system/classes/MenuLinkStruct.php new file mode 100644 index 000000000..c760de15c --- /dev/null +++ b/system/classes/MenuLinkStruct.php @@ -0,0 +1,18 @@ +url = '/' . $url; + } + } +} diff --git a/system/classes/components/CmfiveScriptComponentRegister.php b/system/classes/components/CmfiveScriptComponentRegister.php index 05f3f663a..6a0624a0d 100644 --- a/system/classes/components/CmfiveScriptComponentRegister.php +++ b/system/classes/components/CmfiveScriptComponentRegister.php @@ -12,4 +12,17 @@ public static function outputScripts() { }, static::getComponents() ? : []); } + public static function requireVue3() + { + static::registerComponent('vue3', new CmfiveScriptComponent('/system/templates/base/node_modules/vue3/dist/vue.global.prod.js')); + if (array_key_exists('vue2', static::$_register)) { + unset(static::$_register['vue2']); + } + } + + public static function requireVue2() + { + static::registerComponent('vue2', new CmfiveScriptComponent('/system/templates/js/vue.js')); + } + } \ No newline at end of file diff --git a/system/classes/html/cmfive/DismissableAlert.php b/system/classes/html/cmfive/DismissableAlert.php new file mode 100644 index 000000000..9b3fce76e --- /dev/null +++ b/system/classes/html/cmfive/DismissableAlert.php @@ -0,0 +1,101 @@ +type = $type; + + return $this; + } + + public function setDismissKey(string $dismissKey): self { + $this->dismissKey = $dismissKey; + + return $this; + } + + public function setWeb(\Web $w): self { + $this->w = $w; + + return $this; + } + + /** + * Gets the Bootstrap 5 class of the alert + * + * @return string + */ + private function getAlertClass(): string { + return match ($this->type) { + DismissableAlertType::Primary => 'alert-primary', + DismissableAlertType::Secondary => 'alert-secondary', + DismissableAlertType::Info => 'alert-info', + DismissableAlertType::Warning => 'alert-warning', + DismissableAlertType::Error => 'alert-danger', + }; + } + + /** + * Gets the Bootstrap 5 icon class for the alert + * + * @return string + */ + private function getAlertIcon(): string + { + return match ($this->type) { + DismissableAlertType::Primary => 'bi-info-circle', + DismissableAlertType::Secondary => 'bi-info-circle', + DismissableAlertType::Info => 'bi-info-circle', + DismissableAlertType::Warning => 'bi-exclamation-triangle', + DismissableAlertType::Error => 'bi-exclamation-triangle', + }; + } + + public function __toString(): string { + $setting = \AuthService::getInstance($this->w)->getSettingByKey($this->w->_module . '__' . $this->dismissKey); + if (!empty($setting->id)) { + return ''; + } + + return << + + {$this->content} + + + +RETURN; + } +} \ No newline at end of file diff --git a/system/classes/html/form/InputField.php b/system/classes/html/form/InputField.php index 8999f223f..81e51f614 100755 --- a/system/classes/html/form/InputField.php +++ b/system/classes/html/form/InputField.php @@ -167,7 +167,7 @@ public function setAccept($accept) */ public function setAutocomplete($autocomplete) { - $this->autcomplete = $autocomplete; + $this->autocomplete = $autocomplete; return $this; } diff --git a/system/functions.php b/system/functions.php index e7b8dbc86..e3d5e3faa 100755 --- a/system/functions.php +++ b/system/functions.php @@ -1,5 +1,22 @@ getDeclaringClass()->getName() === get_class($object); +} + /** * Deduplicates arrays of arrays, something that array_unique can't do. * Given an array of arrays, this function will return an array containing only @@ -170,6 +187,10 @@ function humanReadableBytes($input, $rounding = 2, $bytesValue = true) $barrier = 1000; } + if ($input === null) { + return '0 B'; + } + while ($input > $barrier) { $input /= $barrier; array_shift($ext); diff --git a/system/modules/admin/actions/templates/edit.php b/system/modules/admin/actions/templates/edit.php index 3f766b512..cece61826 100755 --- a/system/modules/admin/actions/templates/edit.php +++ b/system/modules/admin/actions/templates/edit.php @@ -106,8 +106,9 @@ function edit_POST(Web $w) $p = $w->pathMatch("id"); $t = $p["id"] ? TemplateService::getInstance($w)->getTemplate($p['id']) : new Template($w); $t->fill($_POST); - $encoded_template_body = $_POST['template_body'] ?? ''; - $t->template_body = json_decode($encoded_template_body); + if (array_key_exists('template_body', $_POST)) { + $t->template_body = json_decode($_POST['template_body']); + } // Set is active if saving is originating from the first page if (isset($_POST["title"]) && isset($_POST["module"]) && isset($_POST["category"])) { diff --git a/system/modules/admin/actions/user/ajax_unlock_account.php b/system/modules/admin/actions/user/ajax_unlock_account.php index 2c6d121bf..745384e8c 100644 --- a/system/modules/admin/actions/user/ajax_unlock_account.php +++ b/system/modules/admin/actions/user/ajax_unlock_account.php @@ -10,7 +10,7 @@ function ajax_unlock_account_POST(Web $w) return; } - $user = $w->Auth->getUser($request_data["id"]); + $user = AuthService::getInstance($w)->getUser($request_data["id"]); if (empty($user)) { $w->out((new AxiosResponse())->setErrorResponse("Unable to find user", null)); return; diff --git a/system/modules/admin/actions/user/edit.php b/system/modules/admin/actions/user/edit.php index 1b6d3c20f..3f56d3893 100644 --- a/system/modules/admin/actions/user/edit.php +++ b/system/modules/admin/actions/user/edit.php @@ -8,6 +8,7 @@ function edit_GET(Web $w) $w->setLayout('layout-bootstrap-5'); VueComponentRegister::registerComponent("autocomplete", new VueComponent("autocomplete", "/system/templates/vue-components/form/elements/autocomplete.vue.js", "/system/templates/vue-components/form/elements/autocomplete.vue.css")); + // CmfiveScriptComponentRegister::registerComponent("toast", new CmfiveScriptComponent("/system/templates/base/dist/Toast.js")); $redirect_url = "/admin/users"; @@ -57,9 +58,9 @@ function edit_GET(Web $w) ], "security" => [ "login" => $user->login, - "is_admin" => $user->is_admin, - "is_active" => $user->is_active, - "is_external" => $user->is_external, + "is_admin" => $user->is_admin ? 'true' : 'false', + "is_active" => $user->is_active ? 'true' : 'false', + "is_external" => $user->is_external ? 'true' : 'false', "is_locked" => $user->is_locked, "new_password" => "", "repeat_new_password" => "", @@ -204,5 +205,4 @@ function edit_POST(Web $w): void } $w->msg("User details updated", $redirect_url); - -} \ No newline at end of file +} diff --git a/system/modules/admin/models/AdminService.php b/system/modules/admin/models/AdminService.php index d9f6dd268..d7aafc429 100755 --- a/system/modules/admin/models/AdminService.php +++ b/system/modules/admin/models/AdminService.php @@ -181,22 +181,37 @@ public function getLanguages(array $where = []): array public function navigation(Web $w, $title = null, $prenav = null) { if ($title) { - $w->ctx("title", $title); + $w->ctx('title', $title); } $nav = $prenav ? $prenav : []; if (AuthService::getInstance($w)->loggedIn()) { - $w->menuLink("admin/index", "Admin Dashboard", $nav); - $w->menuLink("admin/users", "List Users", $nav); - $w->menuLink("admin/groups", "List Groups", $nav); + $w->menuLink('admin/index', 'Admin Dashboard', $nav); + $w->menuLink('admin/users', 'List Users', $nav); + $w->menuLink('admin/groups', 'List Groups', $nav); $w->menuLink('admin-maintenance', 'Maintenance', $nav); - $w->menuLink("admin/lookup", "Lookup", $nav); - $w->menuLink("admin-templates", "Templates", $nav); - // $w->menuLink("admin/composer", "Update composer.json", $nav, null, "_blank"); - $w->menuLink("admin-migration", "Migrations", $nav); + $w->menuLink('admin/lookup', 'Lookup', $nav); + $w->menuLink('admin-templates', 'Templates', $nav); + $w->menuLink('admin-migration', 'Migrations', $nav); } $w->ctx("navigation", $nav); return $nav; } + + /** + * @return MenuLinkStruct[] + */ + public function navList(): array + { + return [ + new MenuLinkStruct('Admin Dashboard', 'admin/index'), + new MenuLinkStruct('List Users', 'admin/users'), + new MenuLinkStruct('List Groups', 'admin/groups'), + new MenuLinkStruct('Maintenance', 'admin-maintenance'), + new MenuLinkStruct('Lookup', 'admin/lookup'), + new MenuLinkStruct('Templates', 'admin-templates'), + new MenuLinkStruct('Migrations', 'admin-migration'), + ]; + } } diff --git a/system/modules/admin/templates/templates/edit.tpl.php b/system/modules/admin/templates/templates/edit.tpl.php index 57e920b97..664e5203b 100755 --- a/system/modules/admin/templates/templates/edit.tpl.php +++ b/system/modules/admin/templates/templates/edit.tpl.php @@ -1,26 +1,27 @@

-
- Details - Template - Test Data - - Manual + +
+
+
-
-
- -
-
- -
-
- -
-
-

- This is the template manual -

+
+
+
+ +
+
+

+ This is the template manual +

+
+
\ No newline at end of file diff --git a/system/modules/admin/templates/user/edit.tpl.php b/system/modules/admin/templates/user/edit.tpl.php index 81caa3429..03c12be37 100644 --- a/system/modules/admin/templates/user/edit.tpl.php +++ b/system/modules/admin/templates/user/edit.tpl.php @@ -1,4 +1,4 @@ -
+

Edit - {{ user.account.firstname + ' ' + user.account.lastname }}

@@ -7,85 +7,68 @@ Groups
-
- -
+
-
+
-
+
This account is locked
-

Update Password

-
- - -
-
- -
+
+
+

Update Password

+
+
+
+
+ + +
+
+ + +
+ +
+
+
-
- - - -
-
- - - +
+
+
+

Google Authenticator

+
+ + +
+
+ + + +
+ +
+
+ + +
+ + +
- -
- -
-
- - -
-
+

Groups

@@ -94,16 +77,10 @@
- - - - - - - \ No newline at end of file diff --git a/system/modules/admin/templates/users.tpl.php b/system/modules/admin/templates/users.tpl.php index 159c39bb8..8c57946fa 100755 --- a/system/modules/admin/templates/users.tpl.php +++ b/system/modules/admin/templates/users.tpl.php @@ -11,10 +11,10 @@ External
-
+
-
+
diff --git a/system/modules/auth/actions/ajax_confirm_mfa_code.php b/system/modules/auth/actions/ajax_confirm_mfa_code.php index 0a937e32a..61ad288c8 100644 --- a/system/modules/auth/actions/ajax_confirm_mfa_code.php +++ b/system/modules/auth/actions/ajax_confirm_mfa_code.php @@ -8,27 +8,27 @@ function ajax_confirm_mfa_code_POST(Web $w) $request_data = json_decode(file_get_contents("php://input"), true); if (empty($request_data) || empty($request_data["id"]) || empty($request_data["mfa_code"])) { - $w->out((new AxiosResponse())->setErrorResponse("Request data missing", null)); + $w->out((new JsonResponse())->setErrorResponse("Request data missing", null)); return; } $user = AuthService::getInstance($w)->getUser($request_data["id"]); if (empty($user)) { - $w->out((new AxiosResponse())->setErrorResponse("Unable to find user", null)); + $w->out((new JsonResponse())->setErrorResponse("Unable to find user", null)); return; } if (!(new GoogleAuthenticator())->checkCode($user->mfa_secret, $request_data["mfa_code"])) { - $w->out((new AxiosResponse())->setErrorResponse("Incorrect MFA Code", null)); + $w->out((new JsonResponse())->setErrorResponse("Incorrect MFA Code", null)); return; } $user->is_mfa_enabled = true; if (!$user->update()) { - $w->out((new AxiosResponse())->setErrorResponse("Failed to confirm MFA code", null)); + $w->out((new JsonResponse())->setErrorResponse("Failed to confirm MFA code", null)); return; } - $w->out((new AxiosResponse())->setSuccessfulResponse("MFA enabled", null)); + $w->out((new JsonResponse())->setSuccessfulResponse("MFA enabled", null)); } diff --git a/system/modules/auth/actions/ajax_disable_mfa.php b/system/modules/auth/actions/ajax_disable_mfa.php index c944947b1..edd7d97d2 100644 --- a/system/modules/auth/actions/ajax_disable_mfa.php +++ b/system/modules/auth/actions/ajax_disable_mfa.php @@ -6,13 +6,13 @@ function ajax_disable_mfa_POST(Web $w) $request_data = json_decode(file_get_contents("php://input"), true); if (empty($request_data) || empty($request_data["id"])) { - $w->out((new AxiosResponse())->setErrorResponse("Request data missing", null)); + $w->out((new JsonResponse())->setErrorResponse("Request data missing", null)); return; } $user = AuthService::getInstance($w)->getUser($request_data["id"]); if (empty($user)) { - $w->out((new AxiosResponse())->setErrorResponse("Unable to find user", null)); + $w->out((new JsonResponse())->setErrorResponse("Unable to find user", null)); return; } @@ -20,9 +20,9 @@ function ajax_disable_mfa_POST(Web $w) $user->mfa_secret = null; if (!$user->update(true)) { - $w->out((new AxiosResponse())->setErrorResponse("Failed to disable MFA", null)); + $w->out((new JsonResponse())->setErrorResponse("Failed to disable MFA", null)); return; } - $w->out((new AxiosResponse())->setSuccessfulResponse("MFA disabled", null)); + $w->out((new JsonResponse())->setSuccessfulResponse("MFA disabled", null)); } diff --git a/system/modules/auth/actions/ajax_get_mfa_qr_code.php b/system/modules/auth/actions/ajax_get_mfa_qr_code.php index 98b57eea3..e6e131a1a 100644 --- a/system/modules/auth/actions/ajax_get_mfa_qr_code.php +++ b/system/modules/auth/actions/ajax_get_mfa_qr_code.php @@ -9,13 +9,13 @@ function ajax_get_mfa_qr_code_GET(Web $w) $user_id = Request::int("id"); if (empty($user_id)) { - $w->out((new AxiosResponse())->setErrorResponse("Request data missing", null)); + $w->out((new JsonResponse())->setErrorResponse("Request data missing", null)); return; } $user = AuthService::getInstance($w)->getUser($user_id); if (empty($user)) { - $w->out((new AxiosResponse())->setErrorResponse("Unable to find user", null)); + $w->out((new JsonResponse())->setErrorResponse("Unable to find user", null)); return; } @@ -23,9 +23,9 @@ function ajax_get_mfa_qr_code_GET(Web $w) $qr_code = GoogleQrUrl::generate(str_replace(" ", "", $user->getFullName()), $user->mfa_secret, str_replace(" ", "", Config::get("main.application_name", "Cmfive"))); if (!$user->update()) { - $w->out((new AxiosResponse())->setErrorResponse("Failed to update generate MFA code", null)); + $w->out((new JsonResponse())->setErrorResponse("Failed to update generate MFA code", null)); return; } - $w->out((new AxiosResponse())->setSuccessfulResponse("User details updated", ["qr_code" => $qr_code, "mfa_secret" => chunk_split($user->mfa_secret, 4, " ")])); + $w->out((new JsonResponse())->setSuccessfulResponse("User details updated", ["qr_code" => $qr_code, "mfa_secret" => chunk_split($user->mfa_secret, 4, " ")])); } diff --git a/system/modules/auth/actions/ajax_update_account_details.php b/system/modules/auth/actions/ajax_update_account_details.php index b8e1caa6e..b39655823 100644 --- a/system/modules/auth/actions/ajax_update_account_details.php +++ b/system/modules/auth/actions/ajax_update_account_details.php @@ -6,13 +6,13 @@ function ajax_update_account_details_POST(Web $w) $request_data = json_decode(file_get_contents("php://input"), true); if (empty($request_data)) { - $w->out((new AxiosResponse())->setErrorResponse("Request data missing", null)); + $w->out((new JsonResponse())->setErrorResponse("Request data missing", null)); return; } $user = AuthService::getInstance($w)->getUser($request_data["id"]); if (empty($user)) { - $w->out((new AxiosResponse())->setErrorResponse("Unable to find user", null)); + $w->out((new JsonResponse())->setErrorResponse("Unable to find user", null)); return; } @@ -21,13 +21,13 @@ function ajax_update_account_details_POST(Web $w) } if (!$user->insertOrUpdate()) { - $w->out((new AxiosResponse())->setErrorResponse("Failed to update details", null)); + $w->out((new JsonResponse())->setErrorResponse("Failed to update details", null)); return; } $contact = $user->getContact(); if (empty($contact)) { - $w->out((new AxiosResponse())->setErrorResponse("Unable to find user", null)); + $w->out((new JsonResponse())->setErrorResponse("Unable to find user", null)); return; } @@ -35,9 +35,9 @@ function ajax_update_account_details_POST(Web $w) $contact->setTitle($request_data["account_details"]["title"]); if (!$contact->insertOrUpdate()) { - $w->out((new AxiosResponse())->setErrorResponse("Failed to update details", null)); + $w->out((new JsonResponse())->setErrorResponse("Failed to update details", null)); return; } - $w->out((new AxiosResponse())->setSuccessfulResponse("User details updated", null)); + $w->out((new JsonResponse())->setSuccessfulResponse("User details updated", null)); } diff --git a/system/modules/auth/actions/ajax_update_password.php b/system/modules/auth/actions/ajax_update_password.php index a98c9435a..79d814a53 100644 --- a/system/modules/auth/actions/ajax_update_password.php +++ b/system/modules/auth/actions/ajax_update_password.php @@ -6,27 +6,27 @@ function ajax_update_password_POST(Web $w) $request_data = json_decode(file_get_contents("php://input"), true); if (empty($request_data) || empty($request_data["id"]) || empty($request_data["new_password"]) || empty($request_data["repeat_new_password"])) { - $w->out((new AxiosResponse())->setErrorResponse("Request data missing", null)); + $w->out((new JsonResponse())->setErrorResponse("Request data missing", null)); return; } if ($request_data["new_password"] !== $request_data["repeat_new_password"]) { - $w->out((new AxiosResponse())->setErrorResponse("Passwords don't match", null)); + $w->out((new JsonResponse())->setErrorResponse("Passwords don't match", null)); return; } $user = AuthService::getInstance($w)->getUser($request_data["id"]); if (empty($user)) { - $w->out((new AxiosResponse())->setErrorResponse("Unable to find user", null)); + $w->out((new JsonResponse())->setErrorResponse("Unable to find user", null)); return; } $user->setPassword($request_data["new_password"]); if (!$user->insertOrUpdate()) { - $w->out((new AxiosResponse())->setErrorResponse("Failed to update password", null)); + $w->out((new JsonResponse())->setErrorResponse("Failed to update password", null)); return; } - $w->out((new AxiosResponse())->setSuccessfulResponse("User password updated", null)); + $w->out((new JsonResponse())->setSuccessfulResponse("User password updated", null)); } diff --git a/system/modules/channels/actions/listchannels.php b/system/modules/channels/actions/listchannels.php index 82019f3ea..6a9499733 100755 --- a/system/modules/channels/actions/listchannels.php +++ b/system/modules/channels/actions/listchannels.php @@ -4,6 +4,7 @@ function listchannels_GET(Web $w) { $w->setLayout('layout-bootstrap-5'); ChannelsService::getInstance($w)->navigation($w, "Channels List"); + History::add('List Channels'); // Get known channel types: email and web $email_channels = ChannelService::getInstance($w)->getEmailChannels() ?? []; diff --git a/system/modules/channels/actions/web/edit.php b/system/modules/channels/actions/web/edit.php index 53a435a76..023fd4ab5 100644 --- a/system/modules/channels/actions/web/edit.php +++ b/system/modules/channels/actions/web/edit.php @@ -13,6 +13,9 @@ function edit_GET(Web $w) $form = $channel_object->getForm(); $web_channel = $channel_id ? ChannelService::getInstance($w)->getWebChannel($channel_id) : new WebChannelOption($w); + if (empty($web_channel)) { + $web_channel = new WebChannelOption($w); + } $form["Web"] = [ [ diff --git a/system/modules/channels/models/ChannelService.php b/system/modules/channels/models/ChannelService.php index 921417a68..1fecabb6c 100755 --- a/system/modules/channels/models/ChannelService.php +++ b/system/modules/channels/models/ChannelService.php @@ -234,4 +234,13 @@ public function navigation(Web $w, $title = null, $prenav = null) $w->ctx("navigation", $nav); return $nav; } + + public function navList(): array + { + return [ + new MenuLinkStruct("List Channels", "channels/listchannels"), + new MenuLinkStruct("List Processors", "channels/listprocessors"), + new MenuLinkStruct("List Messages", "channels/listmessages"), + ]; + } } diff --git a/system/modules/channels/models/ChannelsService.php b/system/modules/channels/models/ChannelsService.php index ae89f7963..a567b77ff 100755 --- a/system/modules/channels/models/ChannelsService.php +++ b/system/modules/channels/models/ChannelsService.php @@ -22,4 +22,13 @@ public function navigation(Web $w, $title = null, $prenav = null) $w->ctx("navigation", $nav); return $nav; } + + public function navList(): array + { + return [ + new MenuLinkStruct("List Channels", "channels/listchannels"), + new MenuLinkStruct("List Processors", "channels/listprocessors"), + new MenuLinkStruct("List Messages", "channels/listmessages"), + ]; + } } diff --git a/system/modules/file/file.hooks.php b/system/modules/file/file.hooks.php index 8f4311a9f..1dbc14bac 100644 --- a/system/modules/file/file.hooks.php +++ b/system/modules/file/file.hooks.php @@ -3,6 +3,13 @@ function file_admin_extra_navigation_items(Web $w) { if (AuthService::getInstance($w)->user()->is_admin == 1) { + if (class_exists('menuLinkStruct') && $w->_layout == 'layout-bootstrap-5') { + return [ + new MenuLinkStruct("File transfer", "file-admin"), + new MenuLinkStruct("Deleted files", "file/deletedfiles") + ]; + } + return [ $w->menuLink("file-admin", "File transfer"), $w->menuLink("file/deletedfiles", "Deleted files") diff --git a/system/modules/form/models/FormService.php b/system/modules/form/models/FormService.php index e261060f9..fd93c9c38 100644 --- a/system/modules/form/models/FormService.php +++ b/system/modules/form/models/FormService.php @@ -606,4 +606,12 @@ public function navigation(Web $w, $title = null, $prenav = null) return $nav; } + + public function navList(): array + { + return [ + new MenuLinkStruct("Applications", "form-application"), + new MenuLinkStruct("Forms", "form"), + ]; + } } diff --git a/system/modules/report/models/ReportService.php b/system/modules/report/models/ReportService.php index aadd02daa..87f327fa9 100755 --- a/system/modules/report/models/ReportService.php +++ b/system/modules/report/models/ReportService.php @@ -664,4 +664,19 @@ public function navigation(Web $w, $title = null, $nav = null) $w->ctx("navigation", $nav); return $nav; } + + public function navList(): array + { + $list = [ + new MenuLinkStruct("Report Dashboard", "report/index") + ]; + if (AuthService::getInstance($this->w)->user()->hasAnyRole(["report_editor", "report_admin"])) { + $list += [ + new MenuLinkStruct("Create a Report", "report/edit"), + new MenuLinkStruct("Connections", "report-connections"), + new MenuLinkStruct("Feeds Dashboard", "report/listfeed"), + ]; + } + return $list; + } } diff --git a/system/modules/search/search.hooks.php b/system/modules/search/search.hooks.php index d242eafba..0dc4c7a29 100644 --- a/system/modules/search/search.hooks.php +++ b/system/modules/search/search.hooks.php @@ -2,6 +2,12 @@ function search_admin_extra_navigation_items(Web $w) { if (AuthService::getInstance($w)->user()->is_admin == 1) { + if (class_exists('menuLinkStruct') && $w->_layout == 'layout-bootstrap-5') { + return [ + new MenuLinkStruct("Search Admin", "search/reindexpage"), + ]; + } + return [ $w->menuLink("search/reindexpage", "Search Admin") ]; diff --git a/system/modules/tag/models/TagService.php b/system/modules/tag/models/TagService.php index f665424a3..e09ddd273 100755 --- a/system/modules/tag/models/TagService.php +++ b/system/modules/tag/models/TagService.php @@ -40,6 +40,15 @@ public function getTagsByObject($object) { return null; } + public function objectHasTag(mixed $object, string $tag): bool + { + $query = $this->_db->get('tag')->leftJoin('tag_assign on tag.id = tag_assign.tag_id') + ->where('object_class', get_class($object))->and('object_id', $object->id) + ->and('tag.tag', $tag) + ->and('tag.is_deleted', 0)->and('tag_assign.is_deleted', 0); + return $query->count() > 0; + } + /** * Returns all tags associated with a given class * @@ -72,4 +81,14 @@ public function navigation(Web $w, $title = null, $nav = null) { $w->ctx("navigation", $nav); return $nav; } + + public function navList(): array + { + if (AuthService::getInstance($this->w)->user()->hasRole('tag_admin')) { + return [ + new MenuLinkStruct("Tag Admin", "tag/admin"), + ]; + } + return []; + } } \ No newline at end of file diff --git a/system/modules/task/models/TaskService.php b/system/modules/task/models/TaskService.php index da2d102c7..4d30666a5 100755 --- a/system/modules/task/models/TaskService.php +++ b/system/modules/task/models/TaskService.php @@ -1104,4 +1104,16 @@ public function navigation(Web $w, $title = null, $nav = null) $w->ctx("navigation", $nav); return $nav; } + + public function navList(): array + { + return [ + new MenuLinkStruct("Task Dashboard", "task/index"), + new MenuLinkStruct("New Task", "task/edit"), + new MenuLinkStruct("Task List", "task/tasklist"), + new MenuLinkStruct("Notifications", "task/notifications"), + new MenuLinkStruct("Activity", "task/taskweek"), + new MenuLinkStruct("Task Groups", "task-group/viewtaskgrouptypes"), + ]; + } } diff --git a/system/modules/timelog/models/TimelogService.php b/system/modules/timelog/models/TimelogService.php index c4e9e91da..a9e7b7707 100755 --- a/system/modules/timelog/models/TimelogService.php +++ b/system/modules/timelog/models/TimelogService.php @@ -220,4 +220,18 @@ public function navigation(Web $w, $title = null, $nav = null) $w->ctx("navigation", $nav); return $nav; } + + public function navList(): array + { + $trackingObject = $this->getTrackingObject(); + + return [ + new MenuLinkStruct("Timelog Dashboard", "timelog/index"), + new MenuLinkStruct( + "Add Timelog", + "timelog/edit" . (!empty($trackingObject) && !empty($trackingObject->id) ? "?class=" . get_class($trackingObject) . "&id=" . $trackingObject->id : ''), + MenuLinkType::Modal + ), + ]; + } } diff --git a/system/templates/base/package-lock.json b/system/templates/base/package-lock.json index 9c390daeb..fe7b17e9a 100644 --- a/system/templates/base/package-lock.json +++ b/system/templates/base/package-lock.json @@ -19,7 +19,8 @@ "quill": "^1.3.6", "tom-select": "^2.2.2", "typescript": "^4.9.5", - "vue": "^2.7.14" + "vue": "^2.7.14", + "vue3": "npm:vue@^3.4.8" }, "devDependencies": { "@types/bootstrap": "^5.2.6", @@ -560,9 +561,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2123,8 +2124,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.20", @@ -2614,6 +2614,63 @@ "@types/node": "*" } }, + "node_modules/@vue/compiler-core": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.8.tgz", + "integrity": "sha512-GjAwOydZV6UyVBi1lYW5v4jjfU6wOeyi3vBATKJOwV7muYF0/nZi4kfdJc0pwdT5lXwbbx57lyA2Y356rFpw1A==", + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/shared": "3.4.8", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.8.tgz", + "integrity": "sha512-GsPyji42zmkSJlaDFKXvwB97ukTlHzlFH/iVzPFYz/APnSzuhu/CMFQbsYmrtsnc2yscF39eC4rKzvKR27aBug==", + "dependencies": { + "@vue/compiler-core": "3.4.8", + "@vue/shared": "3.4.8" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.8.tgz", + "integrity": "sha512-3ZcurOa6bQdZ6VZLtMqYSUZqpsMqfX0MC3oCxQG0VIJFCqouZAgRYJN1c8QvGs7HW5wW8aXVvUOQU0ILVlYHKA==", + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/compiler-core": "3.4.8", + "@vue/compiler-dom": "3.4.8", + "@vue/compiler-ssr": "3.4.8", + "@vue/shared": "3.4.8", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.5", + "postcss": "^8.4.32", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.8.tgz", + "integrity": "sha512-nxN79LHeAemhBpIa2PQ6rz57cW7W4C/XIJCOMSn2g49u6q2ekirmJI0osAOTErQPApOR0KwP2QyeTexX4zQCrw==", + "dependencies": { + "@vue/compiler-dom": "3.4.8", + "@vue/shared": "3.4.8" + } + }, "node_modules/@vue/component-compiler-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", @@ -2681,6 +2738,38 @@ "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true }, + "node_modules/@vue/reactivity": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.8.tgz", + "integrity": "sha512-UJYMQ3S2rqIGw9IvKomD4Xw2uS5VlcKEEmwcfboGOdrI79oqebxnCgTvXWLMClvg3M5SF0Cyn+9eDQoyGMLu9Q==", + "dependencies": { + "@vue/shared": "3.4.8" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.8.tgz", + "integrity": "sha512-sMRXOy89KnwY6fWG5epgPOsCWzpo/64FrA0QkjIeNeGnoA2YyZ6bBUxpFUyqhJ8VbrDhXEFH+6LHMOYrpzX/ZQ==", + "dependencies": { + "@vue/reactivity": "3.4.8", + "@vue/shared": "3.4.8" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.8.tgz", + "integrity": "sha512-L4gZcYo8f3d7rQqQIHkPvyczkjjQ55cJqz2G0v6Ptmqa1mO2zkqN9F8lBT6aGPYy3hd0RDiINbs4jxhSvvy10Q==", + "dependencies": { + "@vue/runtime-core": "3.4.8", + "@vue/shared": "3.4.8", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.8.tgz", + "integrity": "sha512-ChLCWzXiJboQ009oVkemhEoUdrxHme7v3ip+Kh+/kDDeF1WtHWGt0knRLGm1Y4YqCRTSs9QxsZIY8paJj5Szrw==" + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -5153,9 +5242,9 @@ } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d": { "version": "1.0.1", @@ -5690,6 +5779,11 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -8229,6 +8323,17 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -9069,9 +9174,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "funding": [ { "type": "opencollective", @@ -9087,7 +9192,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -11920,6 +12025,60 @@ "source-map": "^0.6.1" } }, + "node_modules/vue3": { + "name": "vue", + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.8.tgz", + "integrity": "sha512-vJffFOe6DqWsAI10v3tDhb1nJrj7CF3CbdQwOznywAsFNoyvrQ1AWQdcIWJpmRpRnw7NFzstzh6fh4w7n1PNdg==", + "dependencies": { + "@vue/compiler-dom": "3.4.8", + "@vue/compiler-sfc": "3.4.8", + "@vue/runtime-dom": "3.4.8", + "@vue/server-renderer": "3.4.8", + "@vue/shared": "3.4.8" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue3/node_modules/@vue/server-renderer": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.8.tgz", + "integrity": "sha512-pBeHM59Owevr3P0Fl1XOjBmq4DTy5JDcjMG4NuzJEVDlZYzY8fHybx0wdjkY5lK5mCtUyBtw6Mz4d87aosc1Sw==", + "dependencies": { + "@vue/compiler-ssr": "3.4.8", + "@vue/shared": "3.4.8" + }, + "peerDependencies": { + "vue": "3.4.8" + } + }, + "node_modules/vue3/node_modules/vue": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.8.tgz", + "integrity": "sha512-vJffFOe6DqWsAI10v3tDhb1nJrj7CF3CbdQwOznywAsFNoyvrQ1AWQdcIWJpmRpRnw7NFzstzh6fh4w7n1PNdg==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.4.8", + "@vue/compiler-sfc": "3.4.8", + "@vue/runtime-dom": "3.4.8", + "@vue/server-renderer": "3.4.8", + "@vue/shared": "3.4.8" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/system/templates/base/package.json b/system/templates/base/package.json index 749ef98ce..e234b4211 100644 --- a/system/templates/base/package.json +++ b/system/templates/base/package.json @@ -25,7 +25,8 @@ "quill": "^1.3.6", "tom-select": "^2.2.2", "typescript": "^4.9.5", - "vue": "^2.7.14" + "vue": "^2.7.14", + "vue3": "npm:vue@^3.4.8" }, "devDependencies": { "@types/bootstrap": "^5.2.6", diff --git a/system/templates/base/src/js/app.ts b/system/templates/base/src/js/app.ts index cc1a05eac..b4bf996c3 100644 --- a/system/templates/base/src/js/app.ts +++ b/system/templates/base/src/js/app.ts @@ -2,7 +2,9 @@ import { AlertAdaptation, DropdownAdaptation, FavouritesAdaptation, TabAdaptation, TableAdaptation } from './adaptations'; import { QuillEditor, InputWithOther, MultiFileUpload, MultiSelect, Overlay, CodeMirror } from './components'; -import { Modal, Tooltip } from 'bootstrap'; +import { Modal, Toast, Tooltip } from 'bootstrap'; + +type window = Window & typeof globalThis & { cmfiveEventBus: Comment, cmfive: { toast: typeof Toast } }; class Cmfive { static THEME_KEY = 'theme'; @@ -128,6 +130,10 @@ class Cmfive { }); } + (window as window).cmfive = { + toast: require('./components/Toast').Toast + } + // Add offset for breadcrumb if scrollbar is visible const breadcrumb = document.querySelector('#breadcrumbs .breadcrumb'); if (breadcrumb) { diff --git a/system/templates/base/src/scss/app.scss b/system/templates/base/src/scss/app.scss index 845e6fa6f..fe95b1337 100644 --- a/system/templates/base/src/scss/app.scss +++ b/system/templates/base/src/scss/app.scss @@ -21,6 +21,7 @@ // Cmfive styles @import 'cmfive/mixins'; +@import 'cmfive/accordion'; @import 'cmfive/breadcrumb'; @import 'cmfive/buttons'; @import 'cmfive/cards'; @@ -78,4 +79,4 @@ body { .clearfix { @include clearfix(); -} \ No newline at end of file +} diff --git a/system/templates/base/src/scss/cmfive/_accordion.scss b/system/templates/base/src/scss/cmfive/_accordion.scss new file mode 100644 index 000000000..d69f3065d --- /dev/null +++ b/system/templates/base/src/scss/cmfive/_accordion.scss @@ -0,0 +1,66 @@ + +.accordion-item { + border-radius: 0; + // @include theme((border-color: 'light-grey')); + + &:first-of-type { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + &:last-of-type { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + .accordion-header { + border-radius: 0; + + .accordion-button { + border-radius: 0; + @include theme((background-color: 'secondary')); + // @include theme((border-color: 'light-grey')); + + &.collapsed { + @include theme((background-color: 'white-black')); + @include theme((color: 'dark-grey')); + + &:after { + @include dark-theme-rule("background-image", url("data:image/svg+xml,")); + } + + &:first-of-type { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + &:last-of-type { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + } + + ::after { + @include theme((color: 'dark-grey')); + } + + &:hover { + cursor: pointer; + } + + &:focus, &:active { + outline: none !important; + box-shadow: none !important; + } + } + } + + .accordion-collapse { + border-radius: 0; + .accordion-body { + border-radius: 0; + @include theme((background-color: 'white-black')); + @include theme((color: 'dark-grey')); + } + } +} \ No newline at end of file diff --git a/system/templates/base/src/scss/cmfive/_breadcrumb.scss b/system/templates/base/src/scss/cmfive/_breadcrumb.scss index bc0486be2..8647836f7 100644 --- a/system/templates/base/src/scss/cmfive/_breadcrumb.scss +++ b/system/templates/base/src/scss/cmfive/_breadcrumb.scss @@ -6,7 +6,7 @@ $themes: map.deep-merge($themes, ( breadcrumb-text: $mid-grey, ), dark: ( - breadcrumb-text: $mid-grey--dark, + breadcrumb-text: $dark-grey--dark, ) )); @@ -44,14 +44,18 @@ $themes: map.deep-merge($themes, ( &:hover { span, a { - @include theme((color: 'dark-grey')); + @include theme((color: 'white')); + } + + &:not(.active) { + text-decoration: underline; } } &.active { max-width: 200px; span, a { - @include theme((color: 'dark-grey')); + @include theme((color: 'white')); } } } diff --git a/system/templates/base/src/scss/cmfive/_buttons.scss b/system/templates/base/src/scss/cmfive/_buttons.scss index 53aabde15..7909f6fdd 100644 --- a/system/templates/base/src/scss/cmfive/_buttons.scss +++ b/system/templates/base/src/scss/cmfive/_buttons.scss @@ -7,16 +7,11 @@ padding: 6px 12px; margin: 4px 0px; border-radius: 2px; - // border: $btn-border-width solid transparent; text-align: center; text-decoration: if($link-decoration == none, null, none); - // white-space: $btn-white-space; vertical-align: middle; user-select: none; - // color: $white; - // background-color: $twopi-blue; - &.btn-sm { @include font-size('caption'); padding: 6px 10px !important; @@ -35,15 +30,21 @@ background-color: darken(map-get($value, "background-color"), 10%) !important; } } - + &.btn-outline-#{$key} { - background-color: $white; - border: 1px solid map-get($value, "background-color"); - color: map-get($value, "background-color"); + @include theme((background-color: $white)); + border-width: 1px; + border-style: solid; + @include theme((border-color: $value)); + // @include theme((color: map-get($value, "color"))); + // border-color: map-get($value, "background-color"); + // color: map-get($value, "background-color"); &:hover { - background-color: map-get($value, "background-color") !important; - color: map-get($value, "color") !important; + @include theme((background-color: map-get($value, "background-color"))); + // @include theme((color: map-get($value, "color"))); + // background-color: map-get($value, "background-color") !important; + // color: map-get($value, "color") !important; } } } @@ -51,7 +52,6 @@ // Remove button - needs consistent styling .bi.bi-x { - // margin-left: 2rem; display: inline-block; border-radius: 50%; height: 20px; diff --git a/system/templates/base/src/scss/cmfive/_menu.scss b/system/templates/base/src/scss/cmfive/_menu.scss index 6270ec5de..6ac17808a 100644 --- a/system/templates/base/src/scss/cmfive/_menu.scss +++ b/system/templates/base/src/scss/cmfive/_menu.scss @@ -23,7 +23,7 @@ $themes: map.deep-merge($themes, ( top: 0px; bottom: 0px; width: $side-menu-width; - z-index: 20; + z-index: 2000; padding: 1rem 0; opacity: 0; overflow-y: auto; @@ -237,7 +237,7 @@ $themes: map.deep-merge($themes, ( #menu-overlay { position: fixed; - z-index: 15; + z-index: 1990; // Sit just under the side menu top: 0; bottom: 0; left: 0; right: 0; background-color: black; diff --git a/system/templates/base/src/scss/cmfive/_type.scss b/system/templates/base/src/scss/cmfive/_type.scss index 392694e22..490e6fde6 100644 --- a/system/templates/base/src/scss/cmfive/_type.scss +++ b/system/templates/base/src/scss/cmfive/_type.scss @@ -51,9 +51,7 @@ small, @include font-size("caption"); } -a:not(.nav-link) { +a:not(.nav-link):not(.btn) { color: $twopi-blue; - // font-size: 16px; - // line-height: 24px; text-decoration: none !important; } diff --git a/system/templates/base/src/scss/cmfive/adaptations/_alerts.scss b/system/templates/base/src/scss/cmfive/adaptations/_alerts.scss index 768726662..e0ece1bcf 100644 --- a/system/templates/base/src/scss/cmfive/adaptations/_alerts.scss +++ b/system/templates/base/src/scss/cmfive/adaptations/_alerts.scss @@ -27,4 +27,8 @@ cursor: pointer; } } + + &.sticky-top { + z-index: 990; + } } \ No newline at end of file diff --git a/system/templates/base/src/scss/cmfive/adaptations/_grid.scss b/system/templates/base/src/scss/cmfive/adaptations/_grid.scss index d12959a0a..18a3e76d7 100644 --- a/system/templates/base/src/scss/cmfive/adaptations/_grid.scss +++ b/system/templates/base/src/scss/cmfive/adaptations/_grid.scss @@ -73,9 +73,26 @@ ul[class*="-block-grid-"] { padding-left: 0; } - &:last-child { - padding-right: 0; - } + // &:last-child { + // padding-right: 0; + // } + } +} + +@media only screen { + ul.small-block-grid-1 li { + padding-left: 0; + padding-right: 0; + } + + ul.medium-block-grid-1 li { + padding-left: 0; + padding-right: 0; + } + + ul.large-block-grid-1 li { + padding-left: 0; + padding-right: 0; } } @@ -84,13 +101,37 @@ ul[class*="-block-grid-"] { ul.small-block-grid-#{$i} { li { flex: 0 1 calc(100%/#{$i}); + padding: 0 1rem; } } - @include media-breakpoint-up(md) { + @include media-breakpoint-down(sm) { + @if $i > 1 { + ul.small-block-grid-#{$i} li:nth-child(#{$i}n) { + padding-right: 0; + } + + ul.small-block-grid-#{$i} li:nth-child(#{$i}n + 1) { + padding-left: 0; + } + } + } + + @include media-breakpoint-only(md) { ul.medium-block-grid-#{$i} { li { flex: 0 1 calc(100%/#{$i}) !important; + padding: 0 1rem; + } + } + + @if $i > 1 { + ul.medium-block-grid-#{$i} li:nth-child(#{$i}n) { + padding-right: 0; + } + + ul.medium-block-grid-#{$i} li:nth-child(#{$i}n + 1) { + padding-left: 0; } } } @@ -99,12 +140,66 @@ ul[class*="-block-grid-"] { ul.large-block-grid-#{$i} { li { flex: 0 1 calc(100%/#{$i}) !important; + padding: 0 1rem; + } + } + + @if $i > 1 { + ul.large-block-grid-#{$i} li:nth-child(#{$i}n) { + padding-right: 0; + } + + ul.large-block-grid-#{$i} li:nth-child(#{$i}n + 1) { + padding-left: 0; } } } } } +// @media only screen { + +// ul.small-block-grid-2 li:nth-child(2n) { +// padding-right: 0; +// } + +// ul.small-block-grid-2 li:nth-child(2n + 1) { +// padding-left: 0; +// } + +// ul.small-block-grid-3 li:nth-child(3n) { +// padding-right: 0; +// } + +// ul.small-block-grid-3 li:nth-child(3n + 1) { +// padding-left: 0; +// } + +// ul.small-block-grid-4 li:nth-child(4n) { +// padding-right: 0; +// } + +// ul.small-block-grid-4 li:nth-child(4n + 1) { +// padding-left: 0; +// } + +// ul.small-block-grid-5 li:nth-child(5n) { +// padding-right: 0; +// } + +// ul.small-block-grid-5 li:nth-child(5n + 1) { +// padding-left: 0; +// } + +// ul.small-block-grid-6 li:nth-child(6n) { +// padding-right: 0; +// } + +// ul.small-block-grid-6 li:nth-child(6n + 1) { +// padding-left: 0; +// } +// } + // Foundation block grid - too complex to include // $include-html-block-grid-classes: true !default; diff --git a/system/templates/base/src/shims-vue.d.ts b/system/templates/base/src/shims-vue.d.ts new file mode 100644 index 000000000..cd04b72a1 --- /dev/null +++ b/system/templates/base/src/shims-vue.d.ts @@ -0,0 +1 @@ +declare module '*.vue'; \ No newline at end of file diff --git a/system/templates/base/tsconfig.json b/system/templates/base/tsconfig.json index b6c2de31c..ca4ae8f3a 100644 --- a/system/templates/base/tsconfig.json +++ b/system/templates/base/tsconfig.json @@ -7,12 +7,13 @@ "preserveConstEnums": true, "sourceMap": true, "experimentalDecorators": true, - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, }, "include": [ "src/**/*.ts", "src/**/*.vue", + "../../modules/*/assets/ts/*", "../../../../../../../modules/*/assets/ts/*" ], - "exclude": ["node_modules", "**/*.spec.ts"] + "exclude": ["node_modules", "**/*.spec.ts"], } \ No newline at end of file diff --git a/system/templates/base/webpack.mix.js b/system/templates/base/webpack.mix.js index b0202512f..51cf81330 100644 --- a/system/templates/base/webpack.mix.js +++ b/system/templates/base/webpack.mix.js @@ -1,35 +1,41 @@ const mix = require("laravel-mix"); -// const fs = require("fs"); const path = require('path'); - const { glb } = require('laravel-mix-glob'); -// const mixGlob = new MixGlob({ -// mix, -// mapping: { -// base: { -// byFunc: { -// scss: { ext: "css" }, -// sass: { ext: "css" }, -// css: { ext: "css" }, -// } -// } -// } -// }); process.env.DEBUG = true; +mix.webpackConfig(webpack => { + return { + plugins: [ + new webpack.DefinePlugin({ __VUE_PROD_DEVTOOLS__: 'true', }), + ] + } +}) + async function loadAssets() { - mix - .ts("src/js/app.ts", "") + mix.ts("src/js/app.ts", "") .vue() .sass("src/scss/app.scss", "") .setPublicPath("dist") .setResourceRoot("/system/templates/base/dist"); - // Compile vue components separately - mix - // .ts('src/js/components/*.vue', 'components/') - .ts(glb.src('../../../../../../../modules/*/assets/ts/*.ts'), 'dist/', null, { + // Compile system module components + mix.ts(glb.src('../../modules/*/assets/ts/*.ts'), 'dist/', null, { + base: function (file, ext, mm) { // mm => micromatch instance + return 'dist/' + path.dirname(file).split(path.sep).reverse()[2] + '/'; + } + }) + .sass(glb.src('../../modules/*/assets/scss/*'), 'dist/', { + sassOptions: { + includePaths: [__dirname + '/src/scss/', __dirname + '/node_modules'] + } + }) + .vue() + .setPublicPath("dist") + .setResourceRoot("/system/templates/base/dist") + + // Compile third party module components + mix.ts(glb.src('../../../../../../../modules/*/assets/ts/*.ts'), 'dist/', null, { base: function (file, ext, mm) { // mm => micromatch instance return 'dist/' + path.dirname(file).split(path.sep).reverse()[2] + '/'; } diff --git a/system/templates/layout-bootstrap-5.tpl.php b/system/templates/layout-bootstrap-5.tpl.php index ae0139be2..5b4af3f14 100644 --- a/system/templates/layout-bootstrap-5.tpl.php +++ b/system/templates/layout-bootstrap-5.tpl.php @@ -3,7 +3,6 @@ ?> - @@ -17,13 +16,12 @@ $w->outputStyles(); ?> - + -
@@ -34,28 +32,18 @@
-
-
+
+
- - - -
Menu
+ +
- -
+
+
+
+
+ Profile +
+
+ Logout +
+
+
+
+

©

+
+
+
-