diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache index 10f0a71..35637cf 100644 --- a/.php-cs-fixer.cache +++ b/.php-cs-fixer.cache @@ -1 +1 @@ -{"php":"8.3.23","version":"3.64.0:v3.64.0#58dd9c931c785a79739310aef5178928305ffa67","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_parentheses":true,"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"fragments\/QuickNavigation\/Dropdown.php":"7cc52d767a1b8fa704e4d89c9a3e48a7","fragments\/QuickNavigation\/MinibarList.php":"6c40eb3f991f48d730f45b8d222914b1","fragments\/QuickNavigation\/NoResult.php":"9b879a1abf902a8aa81cb9e6ce5d921a","fragments\/QuickNavigation\/List.php":"e90ddecd29ce153eec1083841a3088e2","boot.php":"f8d0f81d63e7cbb73a7f52a8a3f01106","pages\/index.php":"daf2b1a819e7a27391553c599d053802","pages\/config.php":"9f7cf272364c09f38e33d861d33e9727","lib\/QuickNavigation\/Button\/WatsonButton.php":"ef0d3641591e83b6e7dfec54008e36a8","lib\/QuickNavigation\/Button\/ButtonRegistry.php":"0ecf7951feeb0b3524fa1b8c92ac6288","lib\/QuickNavigation\/Button\/ArticleNavigationButton.php":"0036d750da4417a5cf81bda896b01de7","lib\/QuickNavigation\/Button\/FavoriteButton.php":"382a3446b216ca0aaef31fa2f21e82d4","lib\/QuickNavigation\/Button\/ArticleHistoryButton.php":"b60589453343c834038edfd26f3c7220","lib\/QuickNavigation\/Button\/CategoryButton.php":"fa0637c3410b461739392b4f4aa50c9d","lib\/QuickNavigation\/Button\/YformButton.php":"b44a64de32aef2e566d0febca56f6128","lib\/QuickNavigation\/Button\/ButtonInterface.php":"943bf3db82d39b6854401e16fe7e36b6","lib\/QuickNavigation\/Minibar\/ArticleHistoryElement.php":"fa606bdfb8e44afdcd46695f36b5d327","lib\/QuickNavigation\/ApiFunction\/MenuRender.php":"fa555351a229780f110eba970ed42a59","lib\/QuickNavigation\/Media\/MediaSorter.php":"161a68caa4895fe8887470db5265ea82","lib\/QuickNavigation\/Media\/QuickNavigationMedia.php":"624d087e36608a981f2276f37737c71a","lib\/QuickNavigation\/Utility\/BuildNavigationArray.php":"d2a23cc394de4fd4e83bb34da8b3c0bc","lib\/QuickNavigation\/QuickNavigationApi.php":"4eabc25eae94790b9095085102dc278a","lib\/QuickNavigation\/LinkMap\/QuickNavigationLinkMap.php":"3060d0975067e724f1dd427eadb02d47","lib\/QuickNavigation\/rex_api_quicknavigation_render.php":"bf8a3f8aa45712984014768a68ba441f","lib\/QuickNavigation\/QuickNavigation.php":"e7879d206a5a091437921d1570285cd9","update.php":"77c556570fd24e393bfa5653a276578f"}} \ No newline at end of file +{"php":"8.3.25","version":"3.64.0:v3.64.0#58dd9c931c785a79739310aef5178928305ffa67","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_parentheses":true,"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"boot.php":"ee95134522a8c820484d7d2cc07c10f2","pages\/config.php":"9f7cf272364c09f38e33d861d33e9727","pages\/index.php":"daf2b1a819e7a27391553c599d053802","fragments\/QuickNavigation\/NoResult.php":"9b879a1abf902a8aa81cb9e6ce5d921a","fragments\/QuickNavigation\/Dropdown.php":"7cc52d767a1b8fa704e4d89c9a3e48a7","fragments\/QuickNavigation\/MinibarList.php":"6c40eb3f991f48d730f45b8d222914b1","fragments\/QuickNavigation\/StructureTreeList.php":"542183facb777d5966dca1b7fc90d4c9","fragments\/QuickNavigation\/List.php":"e90ddecd29ce153eec1083841a3088e2","fragments\/QuickNavigation\/StructureTreeButton.php":"09a6965b26c22db1622537c28061e59b","update.php":"77c556570fd24e393bfa5653a276578f","lib\/QuickNavigation\/QuickNavigation.php":"e7879d206a5a091437921d1570285cd9","lib\/QuickNavigation\/ApiFunction\/MenuRender.php":"fa555351a229780f110eba970ed42a59","lib\/QuickNavigation\/LinkMap\/QuickNavigationLinkMap.php":"3060d0975067e724f1dd427eadb02d47","lib\/QuickNavigation\/Button\/ArticleHistoryButton.php":"b60589453343c834038edfd26f3c7220","lib\/QuickNavigation\/Button\/ButtonInterface.php":"943bf3db82d39b6854401e16fe7e36b6","lib\/QuickNavigation\/Button\/ButtonRegistry.php":"0ecf7951feeb0b3524fa1b8c92ac6288","lib\/QuickNavigation\/Button\/FavoriteButton.php":"382a3446b216ca0aaef31fa2f21e82d4","lib\/QuickNavigation\/Button\/CategoryButton.php":"fa0637c3410b461739392b4f4aa50c9d","lib\/QuickNavigation\/Button\/ArticleNavigationButton.php":"0036d750da4417a5cf81bda896b01de7","lib\/QuickNavigation\/Button\/YformButton.php":"0f33ef81e17add648d4bf64770faf19e","lib\/QuickNavigation\/Button\/WatsonButton.php":"ef0d3641591e83b6e7dfec54008e36a8","lib\/QuickNavigation\/Button\/StructureTreeButton.php":"506774e94bb208c00c4e606af0bf2c59","lib\/QuickNavigation\/QuickNavigationApi.php":"4eabc25eae94790b9095085102dc278a","lib\/QuickNavigation\/Utility\/BuildNavigationArray.php":"5e3c48273c34f08e8cca6162efcf2761","lib\/QuickNavigation\/Minibar\/ArticleHistoryElement.php":"fa606bdfb8e44afdcd46695f36b5d327","lib\/QuickNavigation\/Media\/MediaSorter.php":"161a68caa4895fe8887470db5265ea82","lib\/QuickNavigation\/Media\/QuickNavigationMedia.php":"624d087e36608a981f2276f37737c71a","lib\/QuickNavigation\/rex_api_quicknavigation_render.php":"bf8a3f8aa45712984014768a68ba441f"}} \ No newline at end of file diff --git a/assets/quick-navigation.css b/assets/quick-navigation.css index 149ce63..aacf7ab 100644 --- a/assets/quick-navigation.css +++ b/assets/quick-navigation.css @@ -1,15 +1,81 @@ +/** + * Structure Tree - CSS Variables für Theme Support + * Nach Dashboard2 Pattern implementiert + */ + +/* Standard Variables - Light Theme Default */ :root { --quick-navigation-color-light: rgba(223, 227, 233, 0.59); --quick-navigation-color-base: #9ca5b2; --quick-navigation-color-dark: #324050; - + --quick-navigation-radius: 4px; --quick-navigation-spacing: 24px; --quick-navigation-spacing-list-item-x: 4px; --quick-navigation-spacing-list-item-y: 2px; - + --quick-navigation-bootstrap-z-index: 1030; --quick-navigation-color-offline: #b2b8bb; + + /* Structure Tree Variables */ + --structure-tree-bg: rgba(255, 255, 255, 0.98); + --structure-tree-header-bg: #4b9ad9; + --structure-tree-header-text: #ffffff; + --structure-tree-text: #212529; + --structure-tree-text-muted: #495057; + --structure-tree-border: rgba(108, 117, 125, 0.3); + --structure-tree-hover-bg: rgba(0, 0, 0, 0.05); + --structure-tree-item-border: rgba(108, 117, 125, 0.2); + --structure-tree-domain-color: #0056b3; + --structure-tree-domain-bg: rgba(0, 123, 255, 0.15); + --structure-tree-domain-border: rgba(0, 123, 255, 0.3); +} + +/* System Dark Mode - Fallback für Browser ohne explizites Theme */ +@media (prefers-color-scheme: dark) { + body:not(.rex-theme-light) { + --structure-tree-bg: rgba(44, 62, 80, 0.85); + --structure-tree-header-bg: #34495e; + --structure-tree-header-text: #ecf0f1; + --structure-tree-text: #ecf0f1; + --structure-tree-text-muted: rgba(255, 255, 255, 0.5); + --structure-tree-border: rgba(255, 255, 255, 0.1); + --structure-tree-hover-bg: rgba(255, 255, 255, 0.02); + --structure-tree-item-border: transparent; + --structure-tree-domain-color: rgba(52, 152, 219, 0.8); + --structure-tree-domain-bg: rgba(52, 152, 219, 0.1); + --structure-tree-domain-border: rgba(52, 152, 219, 0.2); + } +} + +/* REDAXO Light Theme - höchste Priorität */ +body.rex-theme-light { + --structure-tree-bg: rgba(255, 255, 255, 0.98) !important; + --structure-tree-header-bg: #4b9ad9 !important; + --structure-tree-header-text: #ffffff !important; + --structure-tree-text: #212529 !important; + --structure-tree-text-muted: #495057 !important; + --structure-tree-border: rgba(108, 117, 125, 0.3) !important; + --structure-tree-hover-bg: rgba(0, 0, 0, 0.05) !important; + --structure-tree-item-border: rgba(108, 117, 125, 0.2) !important; + --structure-tree-domain-color: #0056b3 !important; + --structure-tree-domain-bg: rgba(0, 123, 255, 0.15) !important; + --structure-tree-domain-border: rgba(0, 123, 255, 0.3) !important; +} + +/* REDAXO Dark Theme - höchste Priorität */ +body.rex-theme-dark { + --structure-tree-bg: rgba(44, 62, 80, 0.85) !important; + --structure-tree-header-bg: #34495e !important; + --structure-tree-header-text: #ecf0f1 !important; + --structure-tree-text: #ecf0f1 !important; + --structure-tree-text-muted: rgba(255, 255, 255, 0.5) !important; + --structure-tree-border: rgba(255, 255, 255, 0.1) !important; + --structure-tree-hover-bg: rgba(255, 255, 255, 0.02) !important; + --structure-tree-item-border: transparent !important; + --structure-tree-domain-color: rgba(52, 152, 219, 0.8) !important; + --structure-tree-domain-bg: rgba(52, 152, 219, 0.1) !important; + --structure-tree-domain-border: rgba(52, 152, 219, 0.2) !important; } [data-quick-navigation-toggle="tooltip"] + .tooltip { @@ -152,3 +218,545 @@ { color: var(--quick-navigation-color-offline); } + +/* Structure Tree Off-Canvas Sidebar Styles */ +.structure-tree-sidebar { + position: fixed; + top: 0; + right: -420px; + width: 420px; + height: 100%; + z-index: 10000; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + opacity: 1; + visibility: visible; +} + +body.structure-tree-open .structure-tree-sidebar { + right: 0; +} + +/* Kein Fullscreen Overlay mehr nötig */ + +body.rex-theme-light .structure-tree-sidebar { + --sidebar-bg: #ffffff; + --sidebar-border: #6c757d; + --sidebar-header-bg: #e9ecef; + --sidebar-text: #212529; + --sidebar-link-hover: #dee2e6; + --sidebar-search-bg: #ffffff; + --sidebar-search-border: #6c757d; + --sidebar-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); +} + +body.structure-tree-open .structure-tree-sidebar { + right: 0; +} + +/* Entfernung der alten Transform-basierten Animation */ + +.structure-tree-content { + width: 100%; + height: 100%; + background: var(--structure-tree-bg); + backdrop-filter: blur(12px) saturate(1.3); + -webkit-backdrop-filter: blur(12px) saturate(1.3); + border-left: 1px solid var(--structure-tree-border); + display: flex; + flex-direction: column; + box-shadow: -8px 0 32px rgba(0, 0, 0, 0.4); + color: var(--structure-tree-text); + border-radius: 12px 0 0 12px; +} + + + +.structure-tree-header { + padding: 1rem; + background: var(--structure-tree-header-bg); + border-bottom: 1px solid var(--structure-tree-border); + display: flex; + align-items: center; + justify-content: space-between; + flex-shrink: 0; +} + +.structure-tree-header h4 { + margin: 0; + font-size: 0.95rem; + font-weight: 600; + color: var(--structure-tree-header-text); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.structure-tree-header h4 i { + font-size: 1.3rem; + color: var(--structure-tree-header-text); + opacity: 0.9; +} + + + +.structure-tree-close { + background: none; + border: none; + color: var(--sidebar-text, #ecf0f1); + font-size: 1.2rem; + cursor: pointer; + padding: 0.25rem; + border-radius: 3px; + transition: all 0.2s ease; + opacity: 0.7; + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; +} + +.structure-tree-close:hover { + opacity: 1; + background: rgba(255, 255, 255, 0.2); + transform: scale(1.05); +} + +body.rex-theme-light .structure-tree-close { + color: #ffffff; /* Weiße Schrift im blauen Header */ +} + +body.rex-theme-light .structure-tree-close:hover { + background: rgba(255, 255, 255, 0.2); + color: #ffffff; +} + +.structure-tree-search { + padding: 1rem; + border-bottom: 1px solid var(--sidebar-border, #4a5f7a); + flex-shrink: 0; + background: var(--sidebar-header-bg, #34495e); +} + +.structure-tree-search .input-group { + position: relative; + display: flex; +} + +.structure-tree-search-input { + flex: 1; + padding: 0.75rem 1rem; + border: 1px solid var(--sidebar-search-border, #4a5f7a); + border-radius: 4px; + background: var(--sidebar-search-bg, #2c3e50); + color: var(--sidebar-text, #ecf0f1); + font-size: 1rem; + transition: all 0.2s ease; +} + +.structure-tree-search-input:focus { + outline: none; + border-color: #3498db; + box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); +} + +.structure-tree-search-input::placeholder { + color: rgba(236, 240, 241, 0.5); +} + +body.rex-theme-light .structure-tree-search-input { + background: #ffffff; + color: #212529; + border-color: #6c757d; +} + +body.rex-theme-light .structure-tree-search-input::placeholder { + color: #6c757d; +} + +.structure-tree-search .input-group-addon { + position: absolute; + left: 1rem; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: rgba(236, 240, 241, 0.5); + z-index: 3; +} + +body.rex-theme-light .structure-tree-search .input-group-addon { + display: none; /* Suchsymbol im Light Theme ausblenden */ +} + +/* Light Theme Such-Input anpassen */ +body.rex-theme-light .structure-tree-search-input { + padding-left: 1rem; /* Weniger Padding da kein Icon */ +} + +/* Header Buttons Container */ +.structure-tree-header-buttons { + display: flex; + align-items: center; + gap: 0.5rem; +} + +/* Expand Toggle Button im Header */ +.structure-tree-expand-toggle { + padding: 0.4rem 0.8rem; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + color: var(--structure-tree-header-text); + transition: all 0.2s ease; +} + +.structure-tree-expand-toggle:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.3); + color: var(--structure-tree-header-text); + transform: scale(1.05); +} + +.structure-tree-expand-toggle:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3); +} + +.structure-tree-search-input { + padding-left: 2.5rem; +} + +.structure-tree-clear-search { + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: rgba(236, 240, 241, 0.5); + cursor: pointer; + padding: 0.25rem; + display: none; + transition: all 0.2s ease; + z-index: 3; +} + +.structure-tree-clear-search:hover { + color: var(--sidebar-text, #ecf0f1); +} + +body.rex-theme-light .structure-tree-clear-search { + color: #6c757d; +} + +body.rex-theme-light .structure-tree-clear-search:hover { + color: #212529; +} + +.structure-tree-body { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 0.5rem 1.2rem 0.5rem 0; +} + +.structure-tree-list { + list-style: none; + padding: 0; + margin: 0; +} + +.structure-tree-list li { + margin: 0; + padding: 0; +} + +.structure-tree-item { + display: block; + position: relative; +} + +.structure-tree-item a { + display: flex; + align-items: center; + padding: 0.8rem 7rem 0.8rem 3.2rem; + color: var(--structure-tree-text); + text-decoration: none; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border-left: 3px solid transparent; + font-size: 1rem; + line-height: 1.4; + position: relative; + gap: 0.75rem; + border-radius: 8px; + margin: 0.2rem 0.8rem; + background: var(--structure-tree-hover-bg); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--structure-tree-item-border); +} + +.structure-tree-item a:hover { + background: var(--structure-tree-hover-bg); + text-decoration: none; + color: var(--structure-tree-text); + transform: translateX(4px); + box-shadow: 0 4px 12px rgba(52, 152, 219, 0.2); +} + +.structure-tree-item.structure-tree-current > a { + background: linear-gradient(135deg, rgba(52, 152, 219, 0.25), rgba(52, 152, 219, 0.15)); + border-left-color: #3498db; + color: #3498db; + font-weight: 600; + box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3); + transform: translateX(2px); +} + +.structure-tree-item.structure-tree-current > a:hover { + background: linear-gradient(135deg, rgba(52, 152, 219, 0.35), rgba(52, 152, 219, 0.25)); + transform: translateX(6px); +} + +/* Light Theme für current State */ +body.rex-theme-light .structure-tree-item.structure-tree-current > a { + background: linear-gradient(135deg, rgba(0, 123, 255, 0.25), rgba(0, 123, 255, 0.15)); + border-left-color: #007bff; + color: #0056b3; + font-weight: 600; + box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3); +} + +body.rex-theme-light .structure-tree-item.structure-tree-current > a:hover { + background: linear-gradient(135deg, rgba(0, 123, 255, 0.35), rgba(0, 123, 255, 0.25)); +} + +.structure-tree-toggle { + position: absolute; + right: 0.8rem; + top: 0.8rem; /* Feste Top-Position statt 50% */ + width: 2.2rem; + height: 2.2rem; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 6px; + color: inherit; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 4; + font-size: 1rem; +} + +/* Rotation für Drei-Punkte-Symbol */ +.structure-tree-toggle .rex-icon { + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.structure-tree-item.expanded .structure-tree-toggle .rex-icon { + transform: rotate(90deg); +} + +.structure-tree-toggle:hover { + background: rgba(52, 152, 219, 0.2); + border-color: rgba(52, 152, 219, 0.4); + transform: scale(1.05); + box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3); +} + +body.rex-theme-light .structure-tree-toggle { + background: rgba(108, 117, 125, 0.1); + border-color: rgba(108, 117, 125, 0.2); + color: #495057; +} + +body.rex-theme-light .structure-tree-toggle:hover { + background: rgba(0, 123, 255, 0.15); + border-color: rgba(0, 123, 255, 0.3); + color: #0056b3; + transform: scale(1.05); +} + +/* Entfernt, da wir jetzt festes padding für alle Items haben */ + +.structure-tree-children { + display: none; + margin: 0.5rem 0 0.5rem 0.95rem; + padding-left: 1rem; + border-left: 2px solid rgba(52, 152, 219, 0.3); + border-radius: 0 0 0 8px; + background: rgba(0, 0, 0, 0.1); + overflow: hidden; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.structure-tree-item.expanded .structure-tree-children { + display: block; + animation: slideDown 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + max-height: 0; + } + to { + opacity: 1; + transform: translateY(0); + max-height: 1000px; + } +} + +.structure-tree-children .structure-tree-item a { + font-size: 1.3rem; + padding: 0.6rem 0.8rem; + margin: 0.1rem 0.4rem; + opacity: 0.9; + background: rgba(255, 255, 255, 0.01); + border-radius: 6px; + border-left: 2px solid transparent; +} + +.structure-tree-children .structure-tree-item a:hover { + background: rgba(52, 152, 219, 0.1); + border-left-color: rgba(52, 152, 219, 0.6); + transform: translateX(2px); +} + +/* ID und Domain Styling */ +.rex-primary-id { + color: var(--structure-tree-text-muted); + font-size: 0.9rem; + font-weight: normal; + position: absolute; + right: 5.2rem; + top: 1.2rem; + z-index: 2; +} + +.structure-tree-domain { + color: var(--structure-tree-domain-color); + font-size: 0.8rem; + margin-left: 0.5rem; + padding: 0.1rem 0.4rem; + background: var(--structure-tree-domain-bg); + border-radius: 4px; + border: 1px solid var(--structure-tree-domain-border); +} + +/* Status Indicator Styles */ +.structure-tree-item a::after { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; + background: #2ecc71; /* Online = Grün */ + position: absolute; + right: 3.8rem; /* Position relativ zu festem Toggle */ + top: 1.2rem; /* Feste Top-Position */ + flex-shrink: 0; + box-shadow: 0 0 4px rgba(46, 204, 113, 0.5); + z-index: 2; +} + +/* Offline Status */ +.structure-tree-item a[data-status="0"]::after { + background: #95a5a6; /* Offline = Grau */ + box-shadow: 0 0 4px rgba(149, 165, 166, 0.5); +} + +/* Gesperrt Status */ +.structure-tree-item a[data-status="2"]::after { + background: #e74c3c; /* Gesperrt = Rot */ + box-shadow: 0 0 4px rgba(231, 76, 60, 0.5); +} + +/* Offline Text Color */ +.structure-tree-item a[data-status="0"] { + color: rgba(149, 165, 166, 0.8); +} + +.structure-tree-item a[data-status="0"] span { + opacity: 0.6; +} + +/* Gesperrt Text Color */ +.structure-tree-item a[data-status="2"] { + color: rgba(231, 76, 60, 0.8); +} + +.structure-tree-item a[data-status="2"] span { + opacity: 0.8; +} + +/* Light Theme Status */ +body.rex-theme-light .structure-tree-item a[data-status="0"] { + color: rgba(149, 165, 166, 0.9); +} + +body.rex-theme-light .structure-tree-item a[data-status="2"] { + color: rgba(231, 76, 60, 0.9); +} + +.structure-tree-empty { + padding: 2rem 1rem; + text-align: center; + color: rgba(236, 240, 241, 0.6); +} + +body.rex-theme-light .structure-tree-empty { + color: rgba(73, 80, 87, 0.6); +} + +.structure-tree-empty p { + margin: 0; + font-style: italic; + font-size: 1.3rem; +} + +/* Level-based indentation for nested items */ +.structure-tree-item[data-level="1"] > a { padding-left: 2.5rem; } +.structure-tree-item[data-level="2"] > a { padding-left: 3.5rem; } +.structure-tree-item[data-level="3"] > a { padding-left: 4.5rem; } +.structure-tree-item[data-level="4"] > a { padding-left: 5.5rem; } +.structure-tree-item[data-level="5"] > a { padding-left: 6.5rem; } + +/* Responsive Design */ +@media (max-width: 768px) { + .structure-tree-content { + width: 100%; + max-width: none; + } + + .structure-tree-sidebar { + padding: 0; + } +} + +/* Scrollbar Styling */ +.structure-tree-body::-webkit-scrollbar { + width: 6px; +} + +.structure-tree-body::-webkit-scrollbar-track { + background: transparent; +} + +.structure-tree-body::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 3px; +} + +.structure-tree-body::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} + +body.rex-theme-light .structure-tree-body::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); +} + +body.rex-theme-light .structure-tree-body::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.3); +} diff --git a/assets/quick-navigation.js b/assets/quick-navigation.js index 5e96d1a..7c6f247 100644 --- a/assets/quick-navigation.js +++ b/assets/quick-navigation.js @@ -103,3 +103,233 @@ function quickNavigationGetUrlVars() { return vars; } +// Structure Tree Off-Canvas functionality +$(document).on('rex:ready', function() { + var StructureTree = { + sidebar: null, + overlay: null, + searchInput: null, + + init: function() { + + // Use vanilla JS to check + var sidebarElement = document.getElementById('structure-tree-sidebar'); + this.sidebar = $('#structure-tree-sidebar'); + this.trigger = $('.quick-navigation-button[data-target="#structure-tree-sidebar"]'); + this.searchInput = $('.structure-tree-search-input'); + // Force show for testing + if (this.sidebar.length > 0) { + } + setTimeout(function() { + }, 1000); + this.bindEvents(); + }, + + initializeExpandedStates: function() { + // Alle expanded Items überprüfen und deren direkte Kinder sichtbar machen + $('.structure-tree-item.expanded').each(function() { + var $item = $(this); + var $directChildren = $item.children('.structure-tree-children'); + $directChildren.show(); // Direkt anzeigen ohne Animation beim Laden + }); + }, + + bindEvents: function() { + var self = this; + + // Open sidebar + $(document).on('click', '[data-target="#structure-tree-sidebar"]', function(e) { + e.preventDefault(); + self.open(); + }); + + // Close sidebar + $(document).on('click', '.structure-tree-close, .structure-tree-backdrop', function(e) { + e.preventDefault(); + self.close(); + }); + + // ESC key + $(document).on('keydown', function(e) { + if (e.keyCode === 27 && self.sidebar && self.sidebar.is(':visible')) { + self.close(); + } + }); + + // Tree toggle + $(document).on('click', '.structure-tree-toggle', function(e) { + e.preventDefault(); + e.stopPropagation(); + self.toggleNode($(this)); + }); + + // Search functionality + $(document).on('input', '.structure-tree-search-input', function() { + self.search($(this).val()); + }); + + // Clear search + $(document).on('click', '.structure-tree-clear-search', function() { + self.clearSearch(); + }); + + // Toggle expand/collapse all + $(document).on('click', '.structure-tree-expand-toggle', function(e) { + self.toggleExpandAll(); + }); + + // Navigate on item click + $(document).on('click', '.structure-tree-item a', function(e) { + if (!$(e.target).closest('.structure-tree-toggle').length) { + // Navigate to category + self.close(); + // Let the default link behavior handle navigation + } + }); + }, + + open: function() { + if (this.sidebar) { + this.sidebar.fadeIn(300); + $('body').addClass('structure-tree-open'); + + // Create backdrop if it doesn't exist + if (!$('.structure-tree-backdrop').length) { + $('body').append('
'); + } + $('.structure-tree-backdrop').fadeIn(300); + + if (this.searchInput) { + this.searchInput.focus(); + } + } + }, + + close: function() { + if (this.sidebar) { + this.sidebar.fadeOut(300); + $('.structure-tree-backdrop').fadeOut(300, function() { + $(this).remove(); + }); + $('body').removeClass('structure-tree-open'); + } + }, + + toggleNode: function($toggleBtn) { + var $item = $toggleBtn.closest('.structure-tree-item'); + var $children = $item.children('.structure-tree-children'); + + if ($item.hasClass('expanded')) { + $item.removeClass('expanded'); + $children.slideUp(300); + $toggleBtn.attr('aria-expanded', 'false'); + } else { + $item.addClass('expanded'); + $children.slideDown(300); + $toggleBtn.attr('aria-expanded', 'true'); + } + }, + + search: function(query) { + var $items = $('.structure-tree-item'); + + if (!query.trim()) { + $items.show(); + $('.structure-tree-clear-search').hide(); + return; + } + + $('.structure-tree-clear-search').show(); + query = query.toLowerCase(); + + $items.each(function() { + var $item = $(this); + var text = $item.find('a').text().toLowerCase(); + + if (text.indexOf(query) !== -1) { + $item.show(); + // Show parent items + $item.parents('.structure-tree-children').show(); + $item.parents('.structure-tree-item').show(); + } else { + $item.hide(); + } + }); + }, + + clearSearch: function() { + if (this.searchInput) { + this.searchInput.val(''); + this.search(''); + this.searchInput.focus(); + } + }, + + toggleExpandAll: function() { + var $expandableItems = $('.structure-tree-item:has(.structure-tree-children)'); + var $expandedItems = $('.structure-tree-item.expanded'); + var $toggleBtn = $('.structure-tree-expand-toggle'); + var $icon = $toggleBtn.find('i'); + + // Wenn mehr als die Hälfte expanded ist, collapse all, sonst expand all + var shouldCollapse = $expandedItems.length > ($expandableItems.length / 2); + + if (shouldCollapse) { + // Collapse All + $expandedItems.each(function() { + var $item = $(this); + var $children = $item.children('.structure-tree-children'); + + $item.removeClass('expanded'); + $children.hide(); + $item.find('.structure-tree-toggle').attr('aria-expanded', 'false'); + }); + + // Icon auf "expand" ändern + $icon.removeClass('fa-compress-arrows-alt').addClass('fa-expand-arrows-alt'); + $toggleBtn.attr('title', 'Alle aufklappen'); + + } else { + // Expand All + $expandableItems.each(function() { + var $item = $(this); + var $children = $item.children('.structure-tree-children'); + + if (!$item.hasClass('expanded')) { + $item.addClass('expanded'); + $children.show(); + $item.find('.structure-tree-toggle').attr('aria-expanded', 'true'); + } + }); + + // Icon auf "collapse" ändern + $icon.removeClass('fa-expand-arrows-alt').addClass('fa-compress-arrows-alt'); + $toggleBtn.attr('title', 'Alle zuklappen'); + } + } + }; + + var structureTreeInitialized = false; + + function initializeStructureTree() { + if (structureTreeInitialized) { + return; + } + + if ($('#structure-tree-sidebar').length > 0) { + structureTreeInitialized = true; + StructureTree.init(); + } + } + + // Initialize Structure Tree when QuickNavigation is ready + $(document).on('quick-navigation:ready', function() { + setTimeout(initializeStructureTree, 100); + }); + + // Also try to initialize on document ready as fallback + $(document).ready(function() { + setTimeout(initializeStructureTree, 500); + }); +}); + diff --git a/boot.php b/boot.php index 95f2ae8..7e5cdb6 100644 --- a/boot.php +++ b/boot.php @@ -17,6 +17,7 @@ use FriendsOfRedaxo\QuickNavigation\Button\ArticleNavigationButton; use FriendsOfRedaxo\QuickNavigation\Button\CategoryButton; use FriendsOfRedaxo\QuickNavigation\Button\FavoriteButton; +use FriendsOfRedaxo\QuickNavigation\Button\StructureTreeButton; use FriendsOfRedaxo\QuickNavigation\Button\WatsonButton; use FriendsOfRedaxo\QuickNavigation\Button\YformButton; use FriendsOfRedaxo\QuickNavigation\Linkmap\QuickNavigationLinkMap; @@ -48,6 +49,7 @@ ButtonRegistry::registerButton(new WatsonButton(), 20); ButtonRegistry::registerButton(new CategoryButton(), 30); + ButtonRegistry::registerButton(new StructureTreeButton(), 35); ButtonRegistry::registerButton(new ArticleHistoryButton('structure', 20), 40); ButtonRegistry::registerButton(new YformButton(), 50); ButtonRegistry::registerButton(new FavoriteButton(), 60); diff --git a/fragments/QuickNavigation/StructureTreeButton.php b/fragments/QuickNavigation/StructureTreeButton.php new file mode 100644 index 0000000..1e0eca1 --- /dev/null +++ b/fragments/QuickNavigation/StructureTreeButton.php @@ -0,0 +1,76 @@ +getVar('label', ''); +$icon = $this->getVar('icon', ''); +$buttonClass = $this->getVar('buttonClass', 'btn btn-default'); +$buttonId = $this->getVar('buttonId', ''); +$treeItems = $this->getVar('treeItems', []); +$placeholder = $this->getVar('placeholder', 'Search...'); + +?> + +
+ +
+ + + diff --git a/fragments/QuickNavigation/StructureTreeList.php b/fragments/QuickNavigation/StructureTreeList.php new file mode 100644 index 0000000..a5e4ddf --- /dev/null +++ b/fragments/QuickNavigation/StructureTreeList.php @@ -0,0 +1,16 @@ +getVar('listItems', []); +$cssClass = $this->getVar('cssClass', ''); +?> + + + diff --git a/lang/de_de.lang b/lang/de_de.lang index d4c42c9..a657b0b 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -28,3 +28,9 @@ quick_navigation_yform_add = Datensatz hinzufügen in: quick_navigation_media_sort_alpha = Nach Dateinamen sortieren (A-Z) quick_navigation_media_sort_date = Nach Datum sortieren (neueste zuerst) quick_navigation_media_sort_title = Nach Titel sortieren (A-Z) + +# Structure Tree Navigation +quick_navigation_structure_tree = Strukturbaum +quick_navigation_placeholder_search = Kategorie suchen... +quick_navigation_expand_toggle = Alle auf-/zuklappen +quick_navigation_no_results = Keine Kategorien gefunden diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 7b06c46..a9bb10d 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -23,3 +23,9 @@ quick_navigation_favorite_article_add = Add article to: quick_navigation_favorite_category_add = Add category to: quick_navigation_yform = YForm quick_navigation_yform_add = Add dataset to: + +# Structure Tree Navigation +quick_navigation_structure_tree = Structure Tree +quick_navigation_placeholder_search = Search categories... +quick_navigation_expand_toggle = Expand/collapse all +quick_navigation_no_results = No categories found diff --git a/lib/QuickNavigation/Button/StructureTreeButton.php b/lib/QuickNavigation/Button/StructureTreeButton.php new file mode 100644 index 0000000..3d20fd8 --- /dev/null +++ b/lib/QuickNavigation/Button/StructureTreeButton.php @@ -0,0 +1,116 @@ +hasCurrentInChildren($child['children'])) { + return true; + } + } + return false; + } + + public function renderTreeAsList(array $categoriesArray, int $depth = 0): array + { + $listItems = []; + foreach ($categoriesArray as $item) { + $attributes = [ + 'href' => $item['url'], + 'title' => rex_i18n::msg('quick_navigation_domain') . ' ' . $item['domain'], + 'data-category-id' => $item['id'], + 'data-level' => $depth, + 'data-status' => $item['status'] ?? 1, // Status für Online/Offline/Gesperrt + ]; + + if ($item['current'] === true) { + $attributes['class'] = 'structure-tree-current'; + } + + $hasChildren = !empty($item['children']); + $toggleClass = $hasChildren ? ' structure-tree-has-children' : ''; + + // Prüfe ob dieses Item oder ein Child-Item aktuell ist (für Auto-Expand) + $isInCurrentPath = $item['current'] === true || $this->hasCurrentInChildren($item['children'] ?? []); + if ($isInCurrentPath) { + $toggleClass .= ' expanded'; + } + + $listItem = '
'; + + // Toggle Button für expandierbare Items - rechts positioniert + if ($hasChildren) { + $expandedState = $isInCurrentPath ? 'true' : 'false'; + $iconClass = 'fa-ellipsis-h'; // Immer horizontale Punkte, Rotation erfolgt über CSS + $listItem .= ''; + } + + // Category Link (Icons werden via CSS hinzugefügt) + $listItem .= ''; + $listItem .= '' . rex_escape($item['name']) . ''; + $listItem .= '(' . rex_escape($item['id']) . ')'; + if (!empty($item['domain']) && $item['domain'] !== 'default') { + $listItem .= '' . rex_escape($item['domain']) . ''; + } + $listItem .= ''; + + // Children als verschachteltes UL INNERHALB des Items + if ($hasChildren) { + $fragment = new rex_fragment([ + 'listItems' => $this->renderTreeAsList($item['children'], $depth + 1), + 'cssClass' => 'structure-tree-children', + ]); + $listItem .= $fragment->parse('QuickNavigation/StructureTreeList.php'); + } + + $listItem .= '
'; + + $listItems[] = $listItem; + } + + return $listItems; + } + + public function get(): string + { + // Alle Kategorien anzeigen (auch offline/gesperrt) für vollständigen Baum + $ignoreOffline = false; + + $currentClangId = rex_clang::getCurrentId(); + $categoriesArray = BuildNavigationArray::GenerateBackendNavArray($currentClangId, $ignoreOffline, null); + + // Render Tree Structure + $treeItems = $this->renderTreeAsList($categoriesArray); + + // Search placeholder + $placeholder = rex_i18n::msg('quick_navigation_placeholder_search'); + + // Render Off-Canvas Button (nicht Dropdown) + $fragment = new rex_fragment([ + 'label' => rex_i18n::msg('quick_navigation_structure_tree'), + 'icon' => 'fa-solid fa-sitemap', + 'buttonClass' => 'btn btn-default quick-navigation-button', + 'buttonId' => 'structure-tree-trigger', + 'treeItems' => $treeItems, + 'placeholder' => $placeholder, + ]); + + return $fragment->parse('QuickNavigation/StructureTreeButton.php'); + } +} diff --git a/lib/QuickNavigation/Button/YformButton.php b/lib/QuickNavigation/Button/YformButton.php index 5a70d9a..791fac7 100644 --- a/lib/QuickNavigation/Button/YformButton.php +++ b/lib/QuickNavigation/Button/YformButton.php @@ -19,19 +19,12 @@ class YformButton implements ButtonInterface { public function get(): string { - $yform = rex_addon::get('yform'); - if (version_compare($yform->getVersion(), '5.0.0-beta1', '<')) { - if (!$yform->isAvailable() && !rex_plugin::get('yform', 'manager')->isAvailable()) { - return ''; - } - } - if (version_compare($yform->getVersion(), '5.0.0-beta1', '>=')) { - if (!$yform->isAvailable()) { - return ''; - } + if (!rex_addon::get('yform')->isAvailable() || !rex_plugin::get('yform', 'manager')->isAvailable()) { + return ''; } - $tables = rex_yform_manager_table::getAll(); + $tables = rex_yform_manager_table::getAll(); + $yform = rex_addon::get('yform'); $yperm_suffix = ''; if (version_compare($yform->getVersion(), '4.0.0-dev', '>=')) { $yperm_suffix = '_edit'; diff --git a/lib/QuickNavigation/Utility/BuildNavigationArray.php b/lib/QuickNavigation/Utility/BuildNavigationArray.php index 63fb78e..b26a183 100644 --- a/lib/QuickNavigation/Utility/BuildNavigationArray.php +++ b/lib/QuickNavigation/Utility/BuildNavigationArray.php @@ -89,7 +89,8 @@ public static function GenerateBackendNavArray(int $clangId = null, bool $ignore 'current' => $current, 'domain' => $domainName, 'url' => $backendContext->getUrl(), - 'children' => self::generateBackendNavArray($clangId, $ignoreOffline, $category->getId(), false), + 'status' => $category->getValue('status'), // Status hinzufügen (0=offline, 1=online, 2=gesperrt) + 'children' => self::GenerateBackendNavArray($clangId, $ignoreOffline, $category->getId(), false), ]; } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index a404a04..6a33fff 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,9 +1,9 @@ array( 'name' => '__root__', - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'reference' => '10fbe40ef8b2ccd28f9564ebb15e5d33f5c5aba5', + 'pretty_version' => 'dev-6c1ed4536090b0a405fa1136cbd5e352ac1908ce', + 'version' => 'dev-6c1ed4536090b0a405fa1136cbd5e352ac1908ce', + 'reference' => '6c1ed4536090b0a405fa1136cbd5e352ac1908ce', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -11,9 +11,9 @@ ), 'versions' => array( '__root__' => array( - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'reference' => '10fbe40ef8b2ccd28f9564ebb15e5d33f5c5aba5', + 'pretty_version' => 'dev-6c1ed4536090b0a405fa1136cbd5e352ac1908ce', + 'version' => 'dev-6c1ed4536090b0a405fa1136cbd5e352ac1908ce', + 'reference' => '6c1ed4536090b0a405fa1136cbd5e352ac1908ce', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(),