summaryrefslogtreecommitdiff
path: root/lib/sidebars/menu.rb
blob: 4af385030a809ce9dfc308d894a586ea25eb17ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# frozen_string_literal: true

module Sidebars
  class Menu
    extend ::Gitlab::Utils::Override
    include ::Gitlab::Routing
    include GitlabRoutingHelper
    include Gitlab::Allowable
    include ::Sidebars::Concerns::HasPill
    include ::Sidebars::Concerns::HasIcon
    include ::Sidebars::Concerns::PositionableList
    include ::Sidebars::Concerns::Renderable
    include ::Sidebars::Concerns::ContainerWithHtmlOptions
    include ::Sidebars::Concerns::HasActiveRoutes
    include ::Sidebars::Concerns::HasPartial

    attr_reader :context

    delegate :current_user, :container, to: :@context

    def initialize(context)
      @context = context
      @items = []

      configure_menu_items
    end

    def configure_menu_items
      true
    end

    override :render?
    def render?
      has_renderable_items? || menu_with_partial?
    end

    override :link
    def link
      renderable_items.first&.link
    end

    # This method normalizes the information retrieved from the submenus and this menu
    # Value from menus is something like: [{ path: 'foo', path: 'bar', controller: :foo }]
    # This method filters the information and returns: { path: ['foo', 'bar'], controller: :foo }
    def all_active_routes
      @all_active_routes ||=
        ([active_routes] + renderable_items.map(&:active_routes)).flatten.each_with_object({}) do |pairs, hash|
          pairs.each do |k, v|
            hash[k] ||= []
            hash[k] += Array(v)
            hash[k].uniq!
          end

          hash
        end
    end

    # Returns whether the menu has any menu item, no
    # matter whether it is renderable or not
    def has_items?
      @items.any?
    end

    # Returns all renderable menu items
    def renderable_items
      @renderable_items ||= @items.select(&:render?)
    end

    # Returns a tree-like representation of itself and all
    # renderable menu entries, with additional information
    # on whether the item(s) have an active route
    def serialize_for_super_sidebar
      items = serialize_items_for_super_sidebar
      is_active = @context.route_is_active.call(active_routes) || items.any? { |item| item[:is_active] }

      {
        title: title,
        icon: sprite_icon,
        link: link,
        is_active: is_active,
        pill_count: has_pill? ? pill_count : nil,
        items: items
      }
    end

    # Returns an array of renderable menu entries,
    # with additional information on whether the item
    # has an active route
    def serialize_items_for_super_sidebar
      # All renderable menu entries
      renderable_items.map do |entry|
        entry.serialize_for_super_sidebar.tap do |item|
          active_routes = item.delete(:active_routes)
          item[:is_active] = active_routes ? @context.route_is_active.call(active_routes) : false
        end
      end
    end

    # Returns whether the menu has any renderable menu item
    def has_renderable_items?
      renderable_items.any?
    end

    def add_item(item)
      add_element(@items, item)
    end

    def insert_item_before(before_item, new_item)
      insert_element_before(@items, before_item, new_item)
    end

    def insert_item_after(after_item, new_item)
      insert_element_after(@items, after_item, new_item)
    end

    override :container_html_options
    def container_html_options
      super.tap do |html_options|
        # Flagging menus that can be rendered and with renderable menu items
        if render? && has_renderable_items?
          html_options[:class] = [*html_options[:class], 'has-sub-items'].join(' ')
        end
      end
    end

    # Sometimes we want to convert a top-level Menu (e.g. Wiki/Snippets)
    # to a MenuItem. This serializer is used in order to enable that conversion
    def serialize_as_menu_item_args
      {
        title: title,
        link: link,
        active_routes: active_routes,
        container_html_options: container_html_options
      }
    end

    private

    override :index_of
    def index_of(list, element)
      list.index { |e| e.item_id == element }
    end
  end
end