discourse/app/models/sidebar_url.rb
Martin Brennan 9ef3a18ce4
DEV: Add new experimental admin UI route and sidebar (#23952)
This commit adds a new admin UI under the route `/admin-revamp`, which is
only accessible if the user is in a group defined by the new `enable_experimental_admin_ui_groups` site setting. It
also adds a special `admin` sidebar panel that is shown instead of the `main`
forum one when the admin is in this area.

![image](https://github.com/discourse/discourse/assets/920448/fa0f25e1-e178-4d94-aa5f-472fd3efd787)

We also add an "Admin Revamp" sidebar link to the community section, which
will only appear if the user is in the setting group:

![image](https://github.com/discourse/discourse/assets/920448/ec05ca8b-5a54-442b-ba89-6af35695c104)

Within this there are subroutes defined like `/admin-revamp/config/:area`,
these areas could contain any UI imaginable, this is just laying down an
initial idea of the structure and how the sidebar will work. Sidebar links are
currently hardcoded.

Some other changes:

* Changed the `main` and `chat` panels sidebar panel keys to use exported const values for reuse
* Allowed custom sidebar sections to hide their headers with the `hideSectionHeader` option
* Add a `groupSettingArray` setting on `this.siteSettings` in JS, which accepts a group site setting name
  and splits it by `|` then converts the items in the array to integers, similar to the `_map` magic for ruby
  group site settings
* Adds a `hidden` option for sidebar panels which prevents them from showing in separated mode and prevents
  the switch button from being shown

---------

Co-authored-by: Krzysztof Kotlarek <kotlarek.krzysztof@gmail.com>
2023-10-19 14:23:41 +10:00

97 lines
2.8 KiB
Ruby

# frozen_string_literal: true
class SidebarUrl < ActiveRecord::Base
enum :segment, { primary: 0, secondary: 1 }, scopes: false, suffix: true
FULL_RELOAD_LINKS_REGEX = [%r{\A/my/[a-z_\-/]+\z}, %r{\A/pub/[a-z_\-/]+\z}, %r{\A/safe-mode\z}]
MAX_ICON_LENGTH = 40
MAX_NAME_LENGTH = 80
MAX_VALUE_LENGTH = 1000
COMMUNITY_SECTION_LINKS = [
{
name: "Topics",
path: "/latest",
icon: "layer-group",
segment: SidebarUrl.segments["primary"],
},
{
name: "My Posts",
path: "/my/activity",
icon: "user",
segment: SidebarUrl.segments["primary"],
},
{ name: "Review", path: "/review", icon: "flag", segment: SidebarUrl.segments["primary"] },
{ name: "Admin", path: "/admin", icon: "wrench", segment: SidebarUrl.segments["primary"] },
{
name: "Admin Revamp",
path: "/admin-revamp",
icon: "star",
segment: SidebarUrl.segments["primary"],
},
{ name: "Users", path: "/u", icon: "users", segment: SidebarUrl.segments["secondary"] },
{
name: "About",
path: "/about",
icon: "info-circle",
segment: SidebarUrl.segments["secondary"],
},
{
name: "FAQ",
path: "/faq",
icon: "question-circle",
segment: SidebarUrl.segments["secondary"],
},
{ name: "Groups", path: "/g", icon: "user-friends", segment: SidebarUrl.segments["secondary"] },
{
name: "Badges",
path: "/badges",
icon: "certificate",
segment: SidebarUrl.segments["secondary"],
},
]
validates :icon, presence: true, length: { maximum: MAX_ICON_LENGTH }
validates :name, presence: true, length: { maximum: MAX_NAME_LENGTH }
validates :value, presence: true, length: { maximum: MAX_VALUE_LENGTH }
validate :path_validator
before_validation :remove_internal_hostname, :set_external
def path_validator
return true if !external?
raise ActionController::RoutingError.new("Not Found") if value !~ Discourse::Utils::URI_REGEXP
rescue ActionController::RoutingError
errors.add(
:value,
I18n.t("activerecord.errors.models.sidebar_section_link.attributes.linkable_type.invalid"),
)
end
def remove_internal_hostname
self.value = self.value.sub(%r{\Ahttp(s)?://#{Discourse.current_hostname}}, "")
end
def set_external
self.external = value.start_with?("http://", "https://")
end
def full_reload?
FULL_RELOAD_LINKS_REGEX.any? { |regex| value =~ regex }
end
end
# == Schema Information
#
# Table name: sidebar_urls
#
# id :bigint not null, primary key
# name :string(80) not null
# value :string(1000) not null
# created_at :datetime not null
# updated_at :datetime not null
# icon :string(40) not null
# external :boolean default(FALSE), not null
# segment :integer default("primary"), not null
#