discourse/app/serializers/category_serializer.rb
Renato Atilio eaca2d8a59
FEATURE: Add "Events" category type for Calendar plugin (#39727)
Adds an Events category type alongside Support and Ideas in the
simplified category creation flow. Auto-enables Calendar + Discourse
Post Event, registers the category in events_calendar_categories, and
writes a matching calendar_categories entry. Gated behind
enable_events_category_type_setup.

The Events tab exposes: default calendar view, display weekends, allowed
groups, event date format, topic sorting, and the upcoming events
sidebar link.

Framework support for per-category type settings:

- CategoriesController#create and #update now accept
category_type_settings, slicing the payload by each type's own
configuration_schema_keys(:category_settings). On create the slice
merges into Categories::Configure's category_configuration_values; on
update the controller iterates the category's attached types and calls
configure_category per type, so per-type fields persist on edit and a
type only ever sees its own keys.

- Categories::Types::Base#read_category_settings is a new extension
point types override to return current per-category values; the
CategorySerializer aggregates these as category_type_settings so the
edit form preloads stored values rather than schema defaults.

- Categories::Types::Base forwards :choices through the resolved schema
and lets a type override site setting :type and :choices when it wants a
bool to render as a labelled enum. configure_custom_fields and
configure_site_settings tolerate string or symbol keys.

- SchemaFormField gains enum (FormKit select) and group_list
(GroupChooser) branches. EditCategoryTypeSchemaFields renders a new form
section bound to a category_type_settings form object that iterates
schema.category_settings, gated by a hasCategorySettings getter. tabs.js
seeds the bucket from model.category_type_settings plus schema defaults;
Category#_categoryTypeSaveProperties sends it on save.

---------

Co-authored-by: Martin Brennan <martin@discourse.org>
2026-05-15 18:24:16 -03:00

181 lines
4.6 KiB
Ruby
Vendored

# frozen_string_literal: true
class CategorySerializer < SiteCategorySerializer
include BasicCategoryAttributes
class CategorySettingSerializer < ApplicationSerializer
attributes :auto_bump_cooldown_days,
:num_auto_bump_daily,
:require_reply_approval,
:require_topic_approval,
:nested_replies_default,
:topic_posting_review_mode,
:reply_posting_review_mode
end
class CategoryLocalizationSerializer < ApplicationSerializer
attributes :id, :locale, :name, :description
end
attributes :locale,
:read_restricted,
:available_groups,
:auto_close_hours,
:auto_close_based_on_last_post,
:group_permissions,
:position,
:email_in,
:email_in_allow_strangers,
:mailinglist_mirror,
:all_topics_wiki,
:allow_unlimited_owner_edits_on_first_post,
:can_delete,
:cannot_delete_reason,
:is_special,
:allow_badges,
:custom_fields,
:topic_featured_link_allowed,
:search_priority,
:moderating_group_ids,
:topic_posting_review_group_ids,
:reply_posting_review_group_ids,
:default_slow_mode_seconds,
:style_type,
:emoji,
:icon,
:category_types,
:category_type_settings,
:available_category_types
has_one :category_setting, serializer: CategorySettingSerializer, embed: :objects
has_many :category_localizations, serializer: CategoryLocalizationSerializer, embed: :objects
def include_moderating_group_ids?
SiteSetting.enable_category_group_moderation?
end
def include_category_setting?
object.association(:category_setting).loaded?
end
def group_permissions
@group_permissions ||=
begin
perms =
object
.category_groups
.joins(:group)
.includes(:group)
.merge(Group.visible_groups(scope&.user, "groups.name ASC", include_everyone: true))
.map do |cg|
{
permission_type: cg.permission_type,
group_name: cg.group.name,
group_id: cg.group_id,
}
end
if perms.length == 0 && !object.read_restricted
perms << {
permission_type: CategoryGroup.permission_types[:full],
group_name: Group[:everyone]&.name.presence || :everyone,
group_id: Group::AUTO_GROUPS[:everyone],
}
end
perms
end
end
def include_group_permissions?
scope&.can_edit?(object)
end
def include_available_groups?
scope && scope.can_edit?(object)
end
def available_groups
Group.order(:name).pluck(:name) - group_permissions.map { |g| g[:group_name] }
end
def can_delete
true
end
def include_is_special?
[
SiteSetting.meta_category_id,
SiteSetting.staff_category_id,
SiteSetting.uncategorized_category_id,
].include? object.id
end
def is_special
true
end
def include_can_delete?
scope && scope.can_delete?(object)
end
def include_cannot_delete_reason?
!include_can_delete? && scope && scope.can_edit?(object)
end
def include_email_in?
scope && scope.can_edit?(object)
end
def include_email_in_allow_strangers?
scope && scope.can_edit?(object)
end
def include_notification_level?
scope && scope.user
end
def notification_level
user = scope && scope.user
object.notification_level ||
(user && CategoryUser.where(user: user, category: object).first.try(:notification_level)) ||
CategoryUser.default_notification_level
end
def custom_fields
object.custom_fields
end
def include_custom_fields?
true
end
def name
category_name
end
def description
category_description
end
def category_types
return {} if !SiteSetting.enable_simplified_category_creation
object.category_types
end
def category_type_settings
return {} if !SiteSetting.enable_simplified_category_creation
Categories::TypeRegistry
.all
.values
.reduce({}) do |result, type_klass|
next result unless type_klass.category_matches?(object)
result.merge(type_klass.read_category_settings(object))
end
end
def available_category_types
return [] if !SiteSetting.enable_simplified_category_creation
Categories::TypeRegistry.list(only_visible: true)
end
end