mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-23 00:24:19 +08:00
We believe the rich editor is a great experience for the vast majority of sites and users, so we are enabling it for all sites and all users by default. This commit does the following: * Hides the rich_editor site setting and sets it to true by default. It can still be overridden by sites that want to disable it completely. * Sets `rich_editor` to true for all sites to enable the rich editor everywhere. * Adds a new `default_composition_mode` site setting and corresponding user option that defaults to Rich for all users. The other option is Markdown. * Changes the rich editor toggle in the composer to use the new database-backed user option (`composition_mode`) instead of a local storage key/value store. This makes the preference persistent across devices. Existing key/value store settings for the markdown toggle are kept, the preference will be saved to the user option automatically.
304 lines
11 KiB
Ruby
Vendored
304 lines
11 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
class UserOption < ActiveRecord::Base
|
|
HOMEPAGES = {
|
|
# -1 => reserved for "custom homepage"
|
|
1 => "latest",
|
|
2 => "categories",
|
|
3 => "unread",
|
|
4 => "new",
|
|
5 => "top",
|
|
6 => "bookmarks",
|
|
7 => "unseen",
|
|
8 => "hot",
|
|
}
|
|
|
|
self.ignored_columns = [
|
|
"sidebar_list_destination", # TODO: Remove when 20240212034010_drop_deprecated_columns has been promoted to pre-deploy
|
|
]
|
|
|
|
self.primary_key = :user_id
|
|
belongs_to :user
|
|
before_create :set_defaults
|
|
|
|
before_save :update_hide_profile_and_presence
|
|
after_save :update_tracked_topics
|
|
|
|
scope :human_users, -> { where("user_id > 0") }
|
|
|
|
enum :default_calendar, { none_selected: 0, ics: 1, google: 2 }, scopes: false
|
|
|
|
def self.ensure_consistency!
|
|
sql = <<~SQL
|
|
SELECT u.id FROM users u
|
|
LEFT JOIN user_options o ON o.user_id = u.id
|
|
WHERE o.user_id IS NULL
|
|
SQL
|
|
|
|
DB.query_single(sql).each { |id| UserOption.create(user_id: id) }
|
|
end
|
|
|
|
def self.previous_replies_type
|
|
@previous_replies_type ||= Enum.new(always: 0, unless_emailed: 1, never: 2)
|
|
end
|
|
|
|
def self.like_notification_frequency_type
|
|
@like_notification_frequency_type ||=
|
|
Enum.new(always: 0, first_time_and_daily: 1, first_time: 2, never: 3)
|
|
end
|
|
|
|
def self.text_sizes
|
|
@text_sizes ||= Enum.new(smallest: 4, smaller: 3, normal: 0, larger: 1, largest: 2)
|
|
end
|
|
|
|
def self.title_count_modes
|
|
@title_count_modes ||= Enum.new(notifications: 0, contextual: 1)
|
|
end
|
|
|
|
def self.email_level_types
|
|
@email_level_type ||= Enum.new(always: 0, only_when_away: 1, never: 2)
|
|
end
|
|
|
|
def self.composition_mode_types
|
|
@composition_mode_types ||= Enum.new(markdown: 0, rich: 1)
|
|
end
|
|
|
|
validates :text_size_key, inclusion: { in: UserOption.text_sizes.values }
|
|
validates :email_level, inclusion: { in: UserOption.email_level_types.values }
|
|
validates :email_messages_level, inclusion: { in: UserOption.email_level_types.values }
|
|
validates :timezone, timezone: true
|
|
|
|
def set_defaults
|
|
self.mailing_list_mode = SiteSetting.default_email_mailing_list_mode
|
|
self.mailing_list_mode_frequency = SiteSetting.default_email_mailing_list_mode_frequency
|
|
self.email_level = SiteSetting.default_email_level
|
|
self.email_messages_level = SiteSetting.default_email_messages_level
|
|
self.automatically_unpin_topics = SiteSetting.default_topics_automatic_unpin
|
|
self.email_previous_replies = SiteSetting.default_email_previous_replies
|
|
self.email_in_reply_to = SiteSetting.default_email_in_reply_to
|
|
|
|
self.enable_quoting = SiteSetting.default_other_enable_quoting
|
|
self.enable_smart_lists = SiteSetting.default_other_enable_smart_lists
|
|
self.enable_defer = SiteSetting.default_other_enable_defer
|
|
self.external_links_in_new_tab = SiteSetting.default_other_external_links_in_new_tab
|
|
self.dynamic_favicon = SiteSetting.default_other_dynamic_favicon
|
|
self.skip_new_user_tips = SiteSetting.default_other_skip_new_user_tips
|
|
|
|
self.new_topic_duration_minutes = SiteSetting.default_other_new_topic_duration_minutes
|
|
self.auto_track_topics_after_msecs = SiteSetting.default_other_auto_track_topics_after_msecs
|
|
self.notification_level_when_replying =
|
|
SiteSetting.default_other_notification_level_when_replying
|
|
|
|
self.like_notification_frequency = SiteSetting.default_other_like_notification_frequency
|
|
|
|
self.email_digests = SiteSetting.default_email_digest_frequency.to_i > 0
|
|
self.digest_after_minutes = SiteSetting.default_email_digest_frequency.to_i
|
|
self.include_tl0_in_digests = SiteSetting.default_include_tl0_in_digests
|
|
|
|
self.text_size = SiteSetting.default_text_size
|
|
|
|
self.title_count_mode = SiteSetting.default_title_count_mode
|
|
|
|
self.hide_profile = SiteSetting.default_hide_profile
|
|
self.hide_presence = SiteSetting.default_hide_presence
|
|
self.sidebar_link_to_filtered_list = SiteSetting.default_sidebar_link_to_filtered_list
|
|
self.sidebar_show_count_of_new_items = SiteSetting.default_sidebar_show_count_of_new_items
|
|
self.composition_mode = SiteSetting.default_composition_mode
|
|
|
|
true
|
|
end
|
|
|
|
def mailing_list_mode
|
|
SiteSetting.disable_mailing_list_mode ? false : super
|
|
end
|
|
|
|
def redirected_to_top_yet?
|
|
last_redirected_to_top_at.present?
|
|
end
|
|
|
|
def update_last_redirected_to_top!
|
|
key = "user:#{id}:update_last_redirected_to_top"
|
|
delay = SiteSetting.active_user_rate_limit_secs
|
|
|
|
# only update last_redirected_to_top_at once every minute
|
|
return unless Discourse.redis.setnx(key, "1")
|
|
Discourse.redis.expire(key, delay)
|
|
|
|
# delay the update
|
|
Jobs.enqueue_in(
|
|
delay / 2,
|
|
:update_top_redirection,
|
|
user_id: self.user_id,
|
|
redirected_at: Time.zone.now.to_s,
|
|
)
|
|
end
|
|
|
|
def should_be_redirected_to_top
|
|
redirected_to_top.present?
|
|
end
|
|
|
|
def redirected_to_top
|
|
# redirect is enabled
|
|
return unless SiteSetting.redirect_users_to_top_page
|
|
|
|
# PERF: bypass min_redirected_to_top query for users that were seen already
|
|
return if user.trust_level > 0 && user.last_seen_at && user.last_seen_at > 1.month.ago
|
|
|
|
# top must be in the top_menu
|
|
return unless SiteSetting.top_menu[/\btop\b/i]
|
|
|
|
# not enough topics
|
|
return unless period = SiteSetting.min_redirected_to_top_period(1.day.ago)
|
|
|
|
if !user.seen_before? || (user.trust_level == 0 && !redirected_to_top_yet?)
|
|
update_last_redirected_to_top!
|
|
return { reason: I18n.t("redirected_to_top_reasons.new_user"), period: period }
|
|
elsif user.last_seen_at < 1.month.ago
|
|
update_last_redirected_to_top!
|
|
return { reason: I18n.t("redirected_to_top_reasons.not_seen_in_a_month"), period: period }
|
|
end
|
|
|
|
# don't redirect to top
|
|
nil
|
|
end
|
|
|
|
def treat_as_new_topic_start_date
|
|
duration =
|
|
new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes.to_i
|
|
times = [
|
|
case duration
|
|
when User::NewTopicDuration::ALWAYS
|
|
user.created_at
|
|
when User::NewTopicDuration::LAST_VISIT
|
|
user.previous_visit_at || user.user_stat.new_since
|
|
else
|
|
duration.minutes.ago
|
|
end,
|
|
user.created_at,
|
|
Time.at(SiteSetting.min_new_topics_time).to_datetime,
|
|
]
|
|
|
|
times.max
|
|
end
|
|
|
|
def homepage
|
|
return HOMEPAGES[homepage_id] if HOMEPAGES.keys.include?(homepage_id)
|
|
|
|
"hot" if homepage_id == 8 && SiteSetting.top_menu_map.include?("hot")
|
|
end
|
|
|
|
def text_size
|
|
UserOption.text_sizes[text_size_key]
|
|
end
|
|
|
|
def text_size=(value)
|
|
self.text_size_key = UserOption.text_sizes[value.to_sym]
|
|
end
|
|
|
|
def title_count_mode
|
|
UserOption.title_count_modes[title_count_mode_key]
|
|
end
|
|
|
|
def title_count_mode=(value)
|
|
self.title_count_mode_key = UserOption.title_count_modes[value.to_sym]
|
|
end
|
|
|
|
def unsubscribed_from_all?
|
|
!mailing_list_mode && !email_digests && email_level == UserOption.email_level_types[:never] &&
|
|
email_messages_level == UserOption.email_level_types[:never]
|
|
end
|
|
|
|
def likes_notifications_disabled?
|
|
like_notification_frequency == UserOption.like_notification_frequency_type[:never]
|
|
end
|
|
|
|
def self.user_tzinfo(user_id)
|
|
timezone = UserOption.where(user_id: user_id).pluck(:timezone).first || "UTC"
|
|
|
|
tzinfo = nil
|
|
begin
|
|
tzinfo = ActiveSupport::TimeZone.find_tzinfo(timezone)
|
|
rescue TZInfo::InvalidTimezoneIdentifier
|
|
Rails.logger.warn(
|
|
"#{User.find_by(id: user_id)&.username} has the timezone #{timezone} set, we do not know how to parse it in Rails, fallback to UTC",
|
|
)
|
|
tzinfo = ActiveSupport::TimeZone.find_tzinfo("UTC")
|
|
end
|
|
|
|
tzinfo
|
|
end
|
|
|
|
private
|
|
|
|
def update_hide_profile_and_presence
|
|
if hide_profile_changed? || hide_presence_changed?
|
|
self.hide_profile_and_presence = hide_profile || hide_presence
|
|
elsif hide_profile_and_presence_changed?
|
|
self.hide_profile = hide_profile_and_presence
|
|
self.hide_presence = hide_profile_and_presence
|
|
end
|
|
end
|
|
|
|
def update_tracked_topics
|
|
return unless saved_change_to_auto_track_topics_after_msecs?
|
|
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: user_options
|
|
#
|
|
# allow_private_messages :boolean default(TRUE), not null
|
|
# auto_track_topics_after_msecs :integer
|
|
# automatically_unpin_topics :boolean default(TRUE), not null
|
|
# bookmark_auto_delete_preference :integer default(3), not null
|
|
# composition_mode :integer default(1), not null
|
|
# default_calendar :integer default("none_selected"), not null
|
|
# digest_after_minutes :integer
|
|
# dynamic_favicon :boolean default(FALSE), not null
|
|
# email_digests :boolean
|
|
# email_in_reply_to :boolean default(TRUE), not null
|
|
# email_level :integer default(1), not null
|
|
# email_messages_level :integer default(0), not null
|
|
# email_previous_replies :integer default(2), not null
|
|
# enable_allowed_pm_users :boolean default(FALSE), not null
|
|
# enable_defer :boolean default(FALSE), not null
|
|
# enable_experimental_sidebar :boolean default(FALSE)
|
|
# enable_quoting :boolean default(TRUE), not null
|
|
# enable_smart_lists :boolean default(TRUE), not null
|
|
# external_links_in_new_tab :boolean default(FALSE), not null
|
|
# hide_presence :boolean default(FALSE), not null
|
|
# hide_profile :boolean default(FALSE), not null
|
|
# hide_profile_and_presence :boolean default(FALSE), not null
|
|
# include_tl0_in_digests :boolean default(FALSE)
|
|
# last_redirected_to_top_at :datetime
|
|
# like_notification_frequency :integer default(1), not null
|
|
# mailing_list_mode :boolean default(FALSE), not null
|
|
# mailing_list_mode_frequency :integer default(1), not null
|
|
# new_topic_duration_minutes :integer
|
|
# notification_level_when_replying :integer
|
|
# oldest_search_log_date :datetime
|
|
# seen_popups :integer is an Array
|
|
# sidebar_link_to_filtered_list :boolean default(FALSE), not null
|
|
# sidebar_show_count_of_new_items :boolean default(FALSE), not null
|
|
# skip_new_user_tips :boolean default(FALSE), not null
|
|
# text_size_key :integer default(0), not null
|
|
# text_size_seq :integer default(0), not null
|
|
# theme_ids :integer default([]), not null, is an Array
|
|
# theme_key_seq :integer default(0), not null
|
|
# timezone :string
|
|
# title_count_mode_key :integer default(0), not null
|
|
# topics_unread_when_closed :boolean default(TRUE), not null
|
|
# watched_precedence_over_muted :boolean
|
|
# color_scheme_id :integer
|
|
# dark_scheme_id :integer
|
|
# homepage_id :integer
|
|
# user_id :integer not null, primary key
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_user_options_on_user_id (user_id) UNIQUE
|
|
# index_user_options_on_user_id_and_default_calendar (user_id,default_calendar)
|
|
# index_user_options_on_watched_precedence_over_muted (watched_precedence_over_muted)
|
|
#
|