Skip to content

Commit

Permalink
Add: #406 ユーザーのカスタムCSS (#825)
Browse files Browse the repository at this point in the history
* Add: #406 ユーザーのカスタムCSS

* Fix lint

* Fix lint

* カスタムCSSの保存先を変更

* キャッシュを考慮して別URLに変更
  • Loading branch information
kmycode authored Aug 28, 2024
1 parent 5ffd759 commit 665c632
Show file tree
Hide file tree
Showing 28 changed files with 283 additions and 4 deletions.
22 changes: 21 additions & 1 deletion app/controllers/auth/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ def find_user_from_params
end

def user_params
params.require(:user).permit(:email, :password, :otp_attempt, credential: {})
params.require(:user).permit(:email, :password, :otp_attempt, :disable_css, credential: {})
end

def login_page_params
params.permit(:with_options)
end

def after_sign_in_path_for(resource)
Expand Down Expand Up @@ -113,6 +117,11 @@ def continue_after?
truthy_param?(:continue)
end

def with_login_options?
login_page_params[:with_options] == '1'
end
helper_method :with_login_options?

def restart_session
clear_attempt_from_session
redirect_to new_user_session_path, alert: I18n.t('devise.failure.timeout')
Expand Down Expand Up @@ -151,6 +160,8 @@ def on_authentication_success(user, security_measure)
sign_in(user)
flash.delete(:notice)

disable_custom_css!(user) if disable_custom_css?

LoginActivity.create(
user: user,
success: true,
Expand All @@ -162,6 +173,15 @@ def on_authentication_success(user, security_measure)
UserMailer.suspicious_sign_in(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later! if @login_is_suspicious
end

def disable_custom_css?
user_params[:disable_css].present? && user_params[:disable_css] != '0'
end

def disable_custom_css!(user)
user.settings['web.use_custom_css'] = false
user.save!
end

def suspicious_sign_in?(user)
SuspiciousSignInDetector.new(user).suspicious?(request)
end
Expand Down
12 changes: 11 additions & 1 deletion app/controllers/custom_css_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@ def show
def custom_css_styles
Setting.custom_css
end
helper_method :custom_css_styles

def user_custom_css?
return false if current_user.nil?

current_user.setting_use_custom_css && current_user.custom_css_text.present?
end

def user_custom_css
current_user.custom_css_text
end
helper_method :custom_css_styles, :user_custom_css?, :user_custom_css

def set_user_roles
@user_roles = UserRole.providing_styles
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/settings/preferences/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def user_params
end

def original_user_params
params.require(:user).permit(:locale, :time_zone, chosen_languages: [], settings_attributes: UserSettings.keys)
params.require(:user).permit(:locale, :time_zone, :custom_css_text, chosen_languages: [], settings_attributes: UserSettings.keys)
end

def disabled_visibilities_params
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/settings/preferences/custom_css_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class Settings::Preferences::CustomCssController < Settings::Preferences::BaseController
private

def after_update_redirect_path
settings_preferences_custom_css_path
end
end
16 changes: 16 additions & 0 deletions app/controllers/user_custom_css_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

class UserCustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
before_action :authenticate_user!

def show
render content_type: 'text/css'
end

private

def user_custom_css
current_user.custom_css_text
end
helper_method :user_custom_css
end
12 changes: 12 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end

def user_custom_css?
return false if current_account&.user.nil?

current_account.user.setting_use_custom_css && current_account.user.custom_css_text.present?
end

def user_custom_css_version
return '0' if current_account&.user&.custom_css.nil?

current_account&.user&.custom_css&.updated_at.to_s
end

private

def storage_host_var
Expand Down
4 changes: 4 additions & 0 deletions app/models/concerns/user/has_settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ def setting_hide_favourite_menu
settings['web.hide_favourite_menu']
end

def setting_use_custom_css
settings['web.use_custom_css']
end

def allows_report_emails?
settings['notification_emails.report']
end
Expand Down
16 changes: 16 additions & 0 deletions app/models/custom_css.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

#
# == Schema Information
#
# Table name: custom_csses
#
# id :bigint(8) not null, primary key
# user_id :bigint(8) not null
# css :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class CustomCss < ApplicationRecord
belongs_to :user
end
18 changes: 18 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ class User < ApplicationRecord
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? && !Setting.require_invite_text }
validates :invite_request, presence: true, on: :create, if: :invite_text_required?

has_one :custom_css, inverse_of: :user, dependent: :destroy

validates :email, presence: true, email_address: true

validates_with UserEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? }
Expand Down Expand Up @@ -227,6 +229,22 @@ def update_sign_in!(new_sign_in: false)
prepare_returning_user!
end

def disable_css
false
end

def custom_css_text
custom_css&.css.to_s
end

def custom_css_text=(val)
if custom_css.present?
custom_css.update!(css: val)
else
CustomCss.create!(user: self, css: val)
end
end

def pending?
!approved?
end
Expand Down
1 change: 1 addition & 0 deletions app/models/user_settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class KeyError < Error; end
setting :use_blurhash, default: true
setting :use_pending_items, default: false
setting :use_system_font, default: false
setting :use_custom_css, default: false
setting :content_font_size, default: 'medium', in: %w(medium large x_large xx_large)
setting :bookmark_category_needed, default: false
setting :disable_swiping, default: false
Expand Down
2 changes: 2 additions & 0 deletions app/models/user_settings/setting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ def type
case default_value
when TrueClass, FalseClass
ActiveModel::Type::Boolean.new
when Integer
ActiveModel::Type::Integer.new
else
ActiveModel::Type::String.new
end
Expand Down
9 changes: 9 additions & 0 deletions app/views/auth/sessions/new.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
label: t('simple_form.labels.defaults.password'),
wrapper: :with_label

- if with_login_options?
.fields-group
= f.input :disable_css,
as: :boolean,
hint: false,
input_html: { 'aria-label': t('auth.disable_custom_css') },
label: t('auth.disable_custom_css'),
wrapper: :with_label

.actions
= f.button :button, t('auth.login'), type: :submit

Expand Down
3 changes: 3 additions & 0 deletions app/views/auth/shared/_links.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
- if controller_name != 'passwords' && controller_name != 'registrations'
%li= link_to t('auth.forgot_password'), new_user_password_path

- if controller_name != 'passwords' && controller_name != 'registrations' && params[:with_options].nil?
%li= link_to t('auth.with_login_options'), new_user_session_path(with_options: '1')

- if controller_name != 'confirmations' && (!user_signed_in? || !current_user.confirmed? || current_user.unconfirmed_email.present?)
%li= link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path

Expand Down
3 changes: 3 additions & 0 deletions app/views/layouts/application.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@

= stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all'

- if user_custom_css?
= stylesheet_link_tag user_custom_css_path({ version: user_custom_css_version }), skip_pipeline: true, host: root_url, media: 'all'

= yield :header_tags

%body{ class: body_classes }
Expand Down
30 changes: 30 additions & 0 deletions app/views/settings/preferences/custom_css/show.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
- content_for :page_title do
= t('simple_form.labels.form_admin_settings.custom_css')

- content_for :heading_actions do
= button_tag t('generic.save_changes'), class: 'button', form: 'edit_preferences'

= simple_form_for current_user, url: settings_preferences_custom_css_path, html: { method: :put, id: 'edit_preferences' } do |f|
= render 'shared/error_messages', object: current_user

= f.simple_fields_for :settings, current_user.settings do |ff|
.fields-group
= ff.input :'web.use_custom_css',
hint: false,
label: I18n.t('simple_form.labels.defaults.setting_use_custom_css'),
kmyblue: true,
wrapper: :with_label

.fields-group
= f.input :custom_css_text,
as: :text,
hint: false,
input_html: { rows: 12 },
label: I18n.t('simple_form.labels.defaults.setting_custom_css'),
kmyblue: true,
wrapper: :with_label

%p.hint= t 'simple_form.hints.defaults.setting_custom_css_lead'

.actions
= f.button :button, t('generic.save_changes'), type: :submit
1 change: 1 addition & 0 deletions app/views/user_custom_css/show.css.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= raw user_custom_css %>
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,7 @@ en:
prefix_sign_up: Sign up on Mastodon today!
suffix: With an account, you will be able to follow people, post updates and exchange messages with users from any Mastodon server and more!
didnt_get_confirmation: Didn't receive a confirmation link?
disable_custom_css: Disable custom CSS
dont_have_your_security_key: Don't have your security key?
forgot_password: Forgot your password?
invalid_reset_password_token: Password reset token is invalid or expired. Please request a new one.
Expand Down Expand Up @@ -1478,6 +1479,7 @@ en:
view_strikes: View past strikes against your account
too_fast: Form submitted too fast, try again.
use_security_key: Use security key
with_login_options: Will you disable your custom css?
bookmark_categories:
errors:
limit: Bookmark category limit
Expand Down Expand Up @@ -1906,6 +1908,7 @@ en:
too_few_options: must have more than one item
too_many_options: can't contain more than %{max} items
preferences:
custom_css: Custom css
does_not_search: The full-text search feature is not available on this server. Instead, your posts will be searched according to this setting on other kmyblue servers.
dtl: Deep timeline
dtl_hint: 'You can join deep timeline with #%{tag} tag. Following settings make convenient to use deep timeline.'
Expand Down
3 changes: 3 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,7 @@ ja:
prefix_sign_up: 今すぐMastodonを始めよう!
suffix: アカウントがあれば、どんなMastodon互換サーバーのユーザーでもフォローしたりメッセージをやり取りできるようになります!
didnt_get_confirmation: 確認メールを受信できない場合は
disable_custom_css: カスタムCSSを無効化する
dont_have_your_security_key: セキュリティキーを持っていませんか?
forgot_password: パスワードをお忘れですか?
invalid_reset_password_token: パスワードリセットトークンが正しくないか期限切れです。もう一度リクエストしてください。
Expand Down Expand Up @@ -1384,6 +1385,7 @@ ja:
view_strikes: 過去のストライクを表示
too_fast: フォームの送信が速すぎます。もう一度やり直してください。
use_security_key: セキュリティキーを使用
with_login_options: カスタムCSSを無効化しますか?
challenge:
confirm: 続ける
hint_html: 以後1時間はパスワードの再入力を求めません
Expand Down Expand Up @@ -1782,6 +1784,7 @@ ja:
too_few_options: は複数必要です
too_many_options: は%{max}個までです
preferences:
custom_css: カスタムCSS
does_not_search: このサーバーでは全文検索機能を利用できません。代わりに、他のkmyblueサーバーであなたの投稿がこの設定に従って検索されます。
dtl: ディープタイムライン
dtl_hint: "#%{tag} ハッシュタグに参加することで、ディープタイムラインに投稿できます。ここではディープタイムラインを利用しやすくするための設定ができます。"
Expand Down
3 changes: 3 additions & 0 deletions config/locales/simple_form.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ en:
setting_allow_quote: Subdued quotes are allowed regardless of this setting; you can quote freely from any source except kmyblue!
setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon
setting_bookmark_category_needed: When removing from all category, unbookmarked automatically
setting_custom_css_lead: 'Be sure to remember: In the unlikely event that you make a mistake in entering your custom CSS and the screen does not display properly, you can disable your custom CSS from the link at the bottom of the sign-in screen. Open the sign-in screen in private mode of your browser, for example, and disable it.'
setting_default_searchability: On kmyblue and Fedibird, the search is based on the search permission setting; on Misskey, all public, local public, and non-public posts are searched regardless of this setting; on Mastodon and Firefish, instead of search permission, the "Make public posts freely searchable on other servers" setting in the profile settings is applied. In Mastodon and Firefish, the "Make public posts freely searchable on other servers" setting in the profile settings is applied instead of the search permission.
setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click
setting_disallow_unlisted_public_searchability: この設定を有効にすると、非収載投稿と検索範囲「誰でも」は両立できず不特定多数からの検索が不可になります。Fedibirdと同じ挙動になります
Expand Down Expand Up @@ -263,6 +264,7 @@ en:
medium: Default
x_large: Large large
xx_large: Large large large
setting_custom_css: Custom CSS
setting_default_language: Posting language
setting_default_privacy: Posting privacy
setting_default_reblog_privacy: Reblogging privacy
Expand Down Expand Up @@ -325,6 +327,7 @@ en:
setting_trends: Show today's trends
setting_unfollow_modal: Show confirmation dialog before unfollowing someone
setting_use_blurhash: Show colorful gradients for hidden media
setting_use_custom_css: Enable custom CSS
setting_use_pending_items: Slow mode
setting_use_public_index: Include permitted accounts post to results of search
severity: Severity
Expand Down
3 changes: 3 additions & 0 deletions config/locales/simple_form.ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ ja:
setting_allow_quote: ひかえめな引用はこの設定に関わらず可能です。kmyblue以外からは自由に引用できます
setting_always_send_emails: 通常、Mastodon からメール通知は行われません。
setting_bookmark_category_needed: すべてのカテゴリから削除したとき、ブックマークが自動で外れるようになります
setting_custom_css_lead: '必ず覚えてください: 万が一カスタムCSSの入力を誤り、画面が正常に表示されなくなった場合は、サインイン画面の下にあるリンクよりカスタムCSSを無効化することができます。ブラウザのプライベートモードなどでサインイン画面を開き、無効化してください。'
setting_default_searchability: kmyblue・Fedibirdでは検索許可設定に基づき検索されます。Misskeyでは当設定に関係なく、全ての公開・ローカル公開・非収載投稿が検索されます。Mastodon・Firefishでは検索許可の代わりにプロフィール設定の「公開投稿を他のサーバーで自由に検索できるようにする」設定が適用されます
setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
setting_disallow_unlisted_public_searchability: この設定を有効にすると、非収載投稿と検索範囲「誰でも」は両立できず不特定多数からの検索が不可になります。Fedibirdと同じ挙動になります
Expand Down Expand Up @@ -263,6 +264,7 @@ ja:
medium: デフォルト
x_large: 大きい大きい
xx_large: 大きい大きい大きい
setting_custom_css: カスタムCSS
setting_default_language: 投稿する言語
setting_default_privacy: 投稿の公開範囲
setting_default_reblog_privacy: BTの公開範囲
Expand Down Expand Up @@ -325,6 +327,7 @@ ja:
setting_trends: 本日のトレンドタグを表示する
setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する
setting_use_blurhash: 非表示のメディアを色付きのぼかしで表示する
setting_use_custom_css: カスタムCSSを有効にする
setting_use_pending_items: 手動更新モード
setting_use_public_index: Mastodonの標準設定によって検索が許可されたアカウントの公開投稿を検索結果に含める
severity: 重大性
Expand Down
1 change: 1 addition & 0 deletions config/navigation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
s.item :appearance, safe_join([material_symbol('computer'), t('settings.appearance')]), settings_preferences_appearance_path
s.item :notifications, safe_join([material_symbol('mail'), t('settings.notifications')]), settings_preferences_notifications_path
s.item :reaching, safe_join([material_symbol('search'), t('preferences.reaching')]), settings_preferences_reaching_path
s.item :custom_css, safe_join([material_symbol('inbox'), t('preferences.custom_css')]), settings_preferences_custom_css_path
s.item :other, safe_join([material_symbol('settings'), t('preferences.other')]), settings_preferences_other_path
end

Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def redirect_with_vary(path)
get 'manifest', to: 'manifests#show', defaults: { format: 'json' }
get 'intent', to: 'intents#show'
get 'custom.css', to: 'custom_css#show', as: :custom_css
get 'user_custom.css', to: 'user_custom_css#show', as: :user_custom_css

get 'remote_interaction_helper', to: 'remote_interaction_helper#index'

Expand Down
1 change: 1 addition & 0 deletions config/routes/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
resource :appearance, only: [:show, :update], controller: :appearance
resource :notifications, only: [:show, :update]
resource :reaching, only: [:show, :update], controller: :reaching
resource :custom_css, only: [:show, :update], controller: :custom_css
resource :other, only: [:show, :update], controller: :other
end

Expand Down
Loading

0 comments on commit 665c632

Please sign in to comment.