From 1e6fbdea3a4755ead4f385a382a21e20c35c383d Mon Sep 17 00:00:00 2001 From: noellabo Date: Thu, 10 Feb 2022 05:47:09 +0900 Subject: [PATCH] Add Misskey location, birthday, and isCat --- .../api/v1/accounts/credentials_controller.rb | 2 +- .../settings/preferences_controller.rb | 1 + .../settings/profiles_controller.rb | 2 +- app/helpers/accounts_helper.rb | 20 ++++++ app/helpers/context_helper.rb | 2 + app/javascript/mastodon/components/avatar.js | 15 +++-- .../features/account/components/header.js | 26 ++++++- app/javascript/mastodon/initial_state.js | 1 + app/javascript/mastodon/locales/en.json | 5 +- app/javascript/mastodon/locales/ja.json | 5 +- .../styles/mastodon/components.scss | 21 +++++- app/javascript/styles/mastodon/forms.scss | 4 ++ app/lib/activitypub/case_transform.rb | 11 ++- app/lib/formatter.rb | 40 +++++++++++ app/lib/user_settings_decorator.rb | 5 ++ app/models/account.rb | 17 ----- app/models/concerns/account_avatar.rb | 9 ++- app/models/concerns/account_settings.rb | 67 +++++++++++++++++-- app/models/user.rb | 2 +- .../activitypub/actor_serializer.rb | 13 +++- app/serializers/initial_state_serializer.rb | 1 + app/serializers/rest/account_serializer.rb | 6 +- app/serializers/rest/instance_serializer.rb | 2 + app/serializers/rest/status_serializer.rb | 12 +++- .../activitypub/process_account_service.rb | 24 ++++++- app/views/accounts/_header.html.haml | 2 +- app/views/application/_card.html.haml | 2 +- app/views/directories/index.html.haml | 2 +- app/views/layouts/application.html.haml | 3 + .../preferences/appearance/show.html.haml | 5 ++ app/views/settings/profiles/show.html.haml | 10 +++ app/views/statuses/_detailed_status.html.haml | 2 +- app/views/statuses/_quote_status.html.haml | 2 +- app/views/statuses/_simple_status.html.haml | 11 ++- config/locales/en.yml | 1 + config/locales/ja.yml | 1 + config/locales/simple_form.en.yml | 8 +++ config/locales/simple_form.ja.yml | 8 +++ config/settings.yml | 1 + package.json | 1 + yarn.lock | 5 ++ 41 files changed, 320 insertions(+), 57 deletions(-) diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 64b5cb747..0bc9bbe6a 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController private def account_params - params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value]) + params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, :birthday, :location, fields_attributes: [:name, :value]) end def user_settings_params diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 319636499..a91063924 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -76,6 +76,7 @@ class Settings::PreferencesController < Settings::BaseController :setting_hide_statuses_count, :setting_hide_following_count, :setting_hide_followers_count, + :setting_disable_joke_appearance, notification_emails: %i(follow follow_request reblog favourite emoji_reaction mention digest report pending_account trending_tag), interactions: %i(must_be_follower must_be_following must_be_following_dm) ) diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 0c15447a6..22c34bc77 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -20,7 +20,7 @@ class Settings::ProfilesController < Settings::BaseController private def account_params - params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value]) + params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, :birthday, :location, fields_attributes: [:name, :value]) end def set_account diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index fc307ec70..5e27eaf33 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -98,6 +98,26 @@ module AccountsHelper [prepend_str, account.note].join(' · ') end + def account_cat_params(account, **options) + result = options || {} + result.merge!({ 'data-acct': account.acct }) + + return result unless !current_user&.setting_disable_joke_appearance && account.other_settings['is_cat'] + + @cat_inline_styles ||= {} + @cat_inline_styles[account.acct] = account.cat_ears_color if account.cat_ears_color + + result.merge!({ class: [options[:class], 'cat'].compact.join(' ') }) + end + + def account_cat_styles + return if @cat_inline_styles.nil? + + @cat_inline_styles.map do |acct, color| + ".cat[data-acct=\"#{h(acct)}\"] { --cat-ears-color: #{h(color)}; }" + end.join("\n") + end + def svg_logo content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976') end diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 12122d46f..e454f8683 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -27,6 +27,8 @@ module ContextHelper quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' }, expiry: { 'fedibird' => 'http://fedibird.com/ns#', 'expiry' => 'fedibird:expiry' }, other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' }, + is_cat: { 'misskey' => 'https://misskey-hub.net/ns#', 'isCat' => 'misskey:isCat' }, + vcard: { 'vcard' => 'http://www.w3.org/2006/vcard/ns#' }, }.freeze def full_context diff --git a/app/javascript/mastodon/components/avatar.js b/app/javascript/mastodon/components/avatar.js index 570505833..aade45796 100644 --- a/app/javascript/mastodon/components/avatar.js +++ b/app/javascript/mastodon/components/avatar.js @@ -1,7 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { autoPlayGif } from '../initial_state'; +import classNames from 'classnames'; +import { autoPlayGif, disable_joke_appearance } from '../initial_state'; export default class Avatar extends React.PureComponent { @@ -38,19 +39,18 @@ export default class Avatar extends React.PureComponent { const { hovering } = this.state; const src = account.get('avatar'); + const isCat = !disable_joke_appearance && account.getIn(['other_settings', 'is_cat']); + const catEarsColor = !disable_joke_appearance && account.getIn(['other_settings', 'cat_ears_color']); const staticSrc = account.get('avatar_static'); - let className = 'account__avatar'; - - if (inline) { - className = className + ' account__avatar-inline'; - } + const catEarsColorStyle = catEarsColor ? { '--cat-ears-color': catEarsColor } : {}; const style = { ...this.props.style, width: `${size}px`, height: `${size}px`, backgroundSize: `${size}px ${size}px`, + ...catEarsColorStyle, }; if (hovering || animate) { @@ -61,7 +61,8 @@ export default class Avatar extends React.PureComponent { return (
@@ -408,7 +413,24 @@ class Header extends ImmutablePureComponent { {account.get('note').length > 0 && account.get('note') !== '

' &&
} -
+
+ + + {location && + + + } + {birthday && + + + } + + + + + +
{location}
()
+
{!suspended && ( diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 98eecb11a..12511de10 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -41,5 +41,6 @@ export const show_tab_bar_label = getMeta('show_tab_bar_label'); export const enable_limited_timeline = getMeta('enable_limited_timeline'); export const enableReaction = getMeta('enable_reaction'); export const show_reply_tree_button = getMeta('show_reply_tree_button'); +export const disable_joke_appearance = getMeta('disable_joke_appearance'); export default initialState; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 0369d2f1c..6cc327e21 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -2,8 +2,10 @@ "account.account_note_header": "Note", "account.add_or_remove_from_circle": "Add or Remove from circles", "account.add_or_remove_from_list": "Add or Remove from lists", + "account.age": "{age} years old", "account.badges.bot": "Bot", "account.badges.group": "Group", + "account.birthday": "Birthday", "account.block": "Block @{name}", "account.block_domain": "Block domain {domain}", "account.blocked": "Blocked", @@ -24,9 +26,10 @@ "account.follows.empty": "This user doesn't follow anyone yet.", "account.follows_you": "Follows you", "account.hide_reblogs": "Hide boosts from @{name}", - "account.joined": "Joined {date}", + "account.joined": "Joined", "account.last_status": "Last active", "account.link_verified_on": "Ownership of this link was checked on {date}", + "account.location": "Location", "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.", "account.media": "Media", "account.members": "Members", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 18faf3862..eeb34eb5e 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -2,8 +2,10 @@ "account.account_note_header": "メモ", "account.add_or_remove_from_circle": "サークルから追加または外す", "account.add_or_remove_from_list": "リストから追加または外す", + "account.age": "{age}歳", "account.badges.bot": "Bot", "account.badges.group": "Group", + "account.birthday": "誕生日", "account.block": "@{name}さんをブロック", "account.block_domain": "{domain}全体をブロック", "account.blocked": "ブロック済み", @@ -24,9 +26,10 @@ "account.follows.empty": "まだ誰もフォローしていません。", "account.follows_you": "フォローされています", "account.hide_reblogs": "@{name}さんからのブーストを非表示", - "account.joined": "{date} に登録", + "account.joined": "登録日", "account.last_status": "最後の活動", "account.link_verified_on": "このリンクの所有権は{date}に確認されました", + "account.location": "場所", "account.locked_info": "このアカウントは承認制アカウントです。相手が承認するまでフォローは完了しません。", "account.media": "メディア", "account.members": "参加者", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 959e0e136..d07023086 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1608,7 +1608,7 @@ a .account__avatar { bottom: 0; right: 0; z-index: 1; - + img { @include avatar-radius; width: 100%; @@ -7447,6 +7447,24 @@ noscript { } } + .account__header__personal { + font-size: 14px; + color: $darker-text-color; + margin: 5px auto; + border-collapse: separate; + border-spacing: 10px 3px; + + th { + white-space: nowrap; + } + + &--wrapper { + width: 100%; + margin-top: 10px; + border-top: 1px solid lighten($ui-base-color, 12%); + } + } + .account__header__fields { margin: 0; border-top: 1px solid lighten($ui-base-color, 12%); @@ -7472,6 +7490,7 @@ noscript { font-size: 14px; color: $darker-text-color; padding: 10px 0; + border-top: 1px solid lighten($ui-base-color, 12%); a { display: inline-block; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 4f7e814a4..c5174c7da 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -296,6 +296,10 @@ code { max-width: 50%; } + &-8 { + max-width: 66.67%; + } + .actions { margin-top: 27px; } diff --git a/app/lib/activitypub/case_transform.rb b/app/lib/activitypub/case_transform.rb index ef0c6e1f9..ce4557ad8 100644 --- a/app/lib/activitypub/case_transform.rb +++ b/app/lib/activitypub/case_transform.rb @@ -6,6 +6,15 @@ module ActivityPub::CaseTransform @camel_lower_cache ||= {} end + NON_CONVERSIONS = %w( + _misskey_content + _misskey_quote + _misskey_reaction + _misskey_votes + _misskey_talk + vcard:Address + ).freeze + def camel_lower(value) case value when Array then value.map { |item| camel_lower(item) } @@ -14,7 +23,7 @@ module ActivityPub::CaseTransform when String camel_lower_cache[value] ||= if value.start_with?('_:') '_:' + value.gsub(/\A_:/, '').underscore.camelize(:lower) - elsif value.start_with?('_') + elsif NON_CONVERSIONS.include? value value else value.underscore.camelize(:lower) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index bb1ec029d..871145842 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -28,6 +28,7 @@ class Formatter html = reformat(raw_content) html = apply_inner_link(html) html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify] + html = nyaize_html(html) if options[:nyaize] return html.html_safe # rubocop:disable Rails/OutputSafety end @@ -40,6 +41,7 @@ class Formatter html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify] html = simple_format(html, {}, sanitize: false) html = quotify(html, status) if status.quote? && !options[:escape_quotify] + html = nyaize_html(html) if options[:nyaize] html = html.delete("\n") html.html_safe # rubocop:disable Rails/OutputSafety @@ -210,6 +212,44 @@ class Formatter html.sub(/(<[^>]+>)\z/, "
QT: #{link}
\\1") end + def nyaize_html(html) + inside_anchor = false + + html.split(/(<.+?>)/).compact.map do |x| + if x.match(/^' + inside_anchor = false + end + + if inside_anchor || x[0] == '<' + x + else + x.split(/(:.+?:)/).compact.map do |x| + if x[0] == ':' + x + else + nyaize(x) + end + end.join + end + end.join + end + + def nyaize(text) + text + # ja-JP + .gsub(/な/, "にゃ").gsub(/ナ/, "ニャ").gsub(/ナ/, "ニャ") + # en-US + .gsub(/(?<=n)a/i) { |x| x == 'A' ? 'YA' : 'ya' } + .gsub(/(?<=morn)ing/i) { |x| x == 'ING' ? 'YAN' : 'yan' } + .gsub(/(?<=every)one/i) { |x| x == 'ONE' ? 'NYAN' : 'nyan' } + # vko-KR + .gsub(/[나-낳]/) { |c| (c.ord + '냐'.ord - '나'.ord).chr } + .gsub(/(다)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/m, '다냥') + .gsub(/(야(?=\?))|(야$)|(야(?= ))/m, '냥') + end + def rewrite(text, entities) text = text.to_s diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index be0f22b96..8a7b5484f 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -70,6 +70,7 @@ class UserSettingsDecorator user.settings['hide_statuses_count'] = hide_statuses_count_preference if change?('setting_hide_statuses_count') user.settings['hide_following_count'] = hide_following_count_preference if change?('setting_hide_following_count') user.settings['hide_followers_count'] = hide_followers_count_preference if change?('setting_hide_followers_count') + user.settings['disable_joke_appearance'] = disable_joke_appearance_preference if change?('setting_disable_joke_appearance') end def merged_notification_emails @@ -236,6 +237,10 @@ class UserSettingsDecorator boolean_cast_setting 'setting_hide_followers_count' end + def disable_joke_appearance_preference + boolean_cast_setting 'setting_disable_joke_appearance' + end + def boolean_cast_setting(key) ActiveModel::Type::Boolean.new.cast(settings[key]) end diff --git a/app/models/account.rb b/app/models/account.rb index 8105c4ddc..6554439e6 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -414,23 +414,6 @@ class Account < ApplicationRecord ActionController::Base.helpers.strip_tags(note) end - def settings - self[:settings].class == String ? {} : self[:settings] - end - - def other_settings - local? && user ? settings.merge( - { - 'noindex' => user.setting_noindex, - 'hide_network' => user.setting_hide_network, - 'hide_statuses_count' => user.setting_hide_statuses_count, - 'hide_following_count' => user.setting_hide_following_count, - 'hide_followers_count' => user.setting_hide_followers_count, - 'enable_reaction' => user.setting_enable_reaction, - } - ) : settings - end - class Field < ActiveModelSerializers::Model attributes :name, :value, :verified_at, :account diff --git a/app/models/concerns/account_avatar.rb b/app/models/concerns/account_avatar.rb index f3621cc5d..583b2af7e 100644 --- a/app/models/concerns/account_avatar.rb +++ b/app/models/concerns/account_avatar.rb @@ -6,9 +6,14 @@ module AccountAvatar IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze LIMIT = 2.megabytes + BLURHASH_OPTIONS = { + x_comp: 4, + y_comp: 4, + }.freeze + class_methods do def avatar_styles(file) - styles = { original: { geometry: '400x400#', file_geometry_parser: FastGeometryParser } } + styles = { original: { geometry: '400x400#', file_geometry_parser: FastGeometryParser, blurhash: BLURHASH_OPTIONS } } styles[:static] = { geometry: '400x400#', format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif' styles end @@ -18,7 +23,7 @@ module AccountAvatar included do # Avatar upload - has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '+profile exif' }, processors: [:lazy_thumbnail] + has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '+profile exif' }, processors: [:lazy_thumbnail, :blurhash_transcoder] validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES validates_attachment_size :avatar, less_than: LIMIT remotable_attachment :avatar, LIMIT, suppress_errors: false diff --git a/app/models/concerns/account_settings.rb b/app/models/concerns/account_settings.rb index 11aa79a6f..54e5fb302 100644 --- a/app/models/concerns/account_settings.rb +++ b/app/models/concerns/account_settings.rb @@ -3,24 +3,81 @@ module AccountSettings extend ActiveSupport::Concern + included do + after_initialize :setting_initialize + end + + def cat? + true & settings['is_cat'] + end + + alias cat cat? + + def cat=(val) + settings['is_cat'] = true & ActiveModel::Type::Boolean.new.cast(val) + end + + def cat_ears_color + settings['cat_ears_color'] + end + + def birthday + settings['birthday'] + end + + def birthday=(val) + settings['birthday'] = ActiveRecord::Type::Date.new.cast(val) + end + + def location + settings['location'] + end + + def location=(val) + settings['location'] = val + end + def noindex? - local? ? user&.noindex? : settings['noindex'] + true & (local? ? user&.noindex? : settings['noindex']) end def hide_network? - local? ? user&.hide_network? : settings['hide_network'] + true & (local? ? user&.hide_network? : settings['hide_network']) end def hide_statuses_count? - local? ? user&.hide_statuses_count? : settings['hide_statuses_count'] + true & (local? ? user&.hide_statuses_count? : settings['hide_statuses_count']) end def hide_following_count? - local? ? user&.hide_following_count? : settings['hide_following_count'] + true & (local? ? user&.hide_following_count? : settings['hide_following_count']) end def hide_followers_count? - local? ? user&.hide_followers_count? : settings['hide_followers_count'] + true & (local? ? user&.hide_followers_count? : settings['hide_followers_count']) end + def other_settings + local? && user ? settings.merge( + { + 'noindex' => user.setting_noindex, + 'hide_network' => user.setting_hide_network, + 'hide_statuses_count' => user.setting_hide_statuses_count, + 'hide_following_count' => user.setting_hide_following_count, + 'hide_followers_count' => user.setting_hide_followers_count, + 'enable_reaction' => user.setting_enable_reaction, + } + ) : settings + end + + # Called by blurhash_transcoder + def blurhash=(val) + settings['cat_ears_color'] = "##{Blurhash::Base83::decode83(val.slice(2,4)).to_s(16).rjust(6, '0')}" + end + + private + + def setting_initialize + self[:settings] = {} if has_attribute?(:settings) && self[:settings] === "{}" + end end diff --git a/app/models/user.rb b/app/models/user.rb index c0325ebd8..72340902d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -131,7 +131,7 @@ class User < ApplicationRecord :follow_button_to_list_adder, :show_navigation_panel, :show_quote_button, :show_bookmark_button, :place_tab_bar_at_bottom,:show_tab_bar_label, :enable_limited_timeline, :enable_reaction, :show_reply_tree_button, - :hide_statuses_count, :hide_following_count, :hide_followers_count, + :hide_statuses_count, :hide_following_count, :hide_followers_count, :disable_joke_appearance, to: :settings, prefix: :setting, allow_nil: false diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 3bec3abef..215afb5c8 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -7,7 +7,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer context_extensions :manually_approves_followers, :featured, :also_known_as, :moved_to, :property_value, :identity_proof, - :discoverable, :olm, :suspended, :other_setting + :discoverable, :olm, :suspended, :other_setting, + :vcard attributes :id, :type, :following, :followers, :inbox, :outbox, :featured, :featured_tags, @@ -24,6 +25,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer attribute :moved_to, if: :moved? attribute :also_known_as, if: :also_known_as? attribute :suspended, if: :suspended? + attribute :bday, key: :'vcard:bday' + attribute :address, key: :'vcard:Address' has_many :virtual_other_settings, key: :other_setting @@ -164,6 +167,14 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer object.created_at.midnight.iso8601 end + def bday + object.birthday + end + + def address + object.location + end + def virtual_other_settings object.other_settings.map do |k, v| { diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 714774f44..8599551e8 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -55,6 +55,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:enable_limited_timeline] = object.current_account.user.setting_enable_limited_timeline store[:enable_reaction] = object.current_account.user.setting_enable_reaction store[:show_reply_tree_button] = object.current_account.user.setting_show_reply_tree_button + store[:disable_joke_appearance] = object.current_account.user.setting_disable_joke_appearance else store[:auto_play_gif] = Setting.auto_play_gif store[:display_media] = Setting.display_media diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 73af4c4b0..72ac0f508 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -3,7 +3,7 @@ class REST::AccountSerializer < ActiveModel::Serializer include RoutingHelper - attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at, + attributes :id, :username, :acct, :display_name, :locked, :bot, :cat, :discoverable, :group, :created_at, :note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count, :following_count, :subscribing_count, :statuses_count, :last_status_at @@ -88,6 +88,10 @@ class REST::AccountSerializer < ActiveModel::Serializer object.suspended? ? false : object.bot end + def cat + object.suspended? ? false : object.cat + end + def discoverable object.suspended? ? false : object.discoverable end diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index eccdabfec..a88ba35dd 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -127,6 +127,8 @@ class REST::InstanceSerializer < ActiveModel::Serializer :visibility_mutual, :visibility_limited, :emoji_reaction, + :misskey_birthday, + :misskey_location, ] end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 25d4e7d1f..3b4e76666 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -17,6 +17,8 @@ class REST::StatusSerializer < ActiveModel::Serializer attribute :content, unless: :source_requested? attribute :text, if: :source_requested? + attribute :nyaize_content, if: :joke_applied? + attribute :quote_id, if: :quote? attribute :expires_at, if: :has_expires? @@ -119,7 +121,11 @@ class REST::StatusSerializer < ActiveModel::Serializer end def content - Formatter.instance.format(object) + @content ||= Formatter.instance.format(object) + end + + def nyaize_content + @nyaize_content ||= Formatter.instance.format(object, nyaize: object.account.cat?) end def url @@ -188,6 +194,10 @@ class REST::StatusSerializer < ActiveModel::Serializer instance_options[:source_requested] end + def joke_applied? + !source_requested? && object.account.cat? && nyaize_content != content + end + def ordered_mentions object.active_mentions.to_a.sort_by(&:id) end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 4c8f9ee4f..b89c7d325 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -98,7 +98,7 @@ class ActivityPub::ProcessAccountService < BaseService @account.note = @json['summary'] || '' @account.locked = @json['manuallyApprovesFollowers'] || false @account.fields = property_values || {} - @account.settings = defer_settings.merge(other_settings || {}) + @account.settings = defer_settings.merge(other_settings, birthday, address, is_cat) @account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) } @account.discoverable = @json['discoverable'] || false end @@ -212,16 +212,34 @@ class ActivityPub::ProcessAccountService < BaseService as_array(@json['attachment']).select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') } end + def birthday + return {} if @json['vcard:bday'].blank? + { 'birthday' => ActiveRecord::Type::Date.new.cast(@json['vcard:bday']) } + end + + def address + return {} if @json['vcard:Address'].blank? + { 'location' => @json['vcard:Address'] } + end + + def is_cat + return {} unless ActiveModel::Type::Boolean.new.cast(@json['isCat']) + { 'is_cat' => true } + end + DEFER_SETTINGS_KEYS = %w( + birthday + location + cat_ears_color noindex ).freeze def defer_settings - (@account.settings || {}).select { |key, _| DEFER_SETTINGS_KEYS.include?(key) } + @account.settings.select { |key, _| DEFER_SETTINGS_KEYS.include?(key) } end def other_settings - return unless @json['otherSetting'].is_a?(Array) + return {} unless @json['otherSetting'].is_a?(Array) @json['otherSetting'].each_with_object({}) { |v, h| h.merge!({v['name'] => v['value']}) if v['type'] == 'PropertyValue' } end diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 7f5da8c19..ff6797cfc 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -2,7 +2,7 @@ .public-account-header__image = image_tag (prefers_autoplay? ? account.header_original_url : account.header_static_url), class: 'parallax' .public-account-header__bar - = link_to short_account_url(account), class: 'avatar' do + = link_to short_account_url(account), account_cat_params(account, class: 'avatar') do = image_tag (prefers_autoplay? ? account.avatar_original_url : account.avatar_static_url), id: 'profile_page_avatar', data: { original: full_asset_url(account.avatar_original_url), static: full_asset_url(account.avatar_static_url), autoplay: prefers_autoplay? } .public-account-header__tabs .public-account-header__tabs__name diff --git a/app/views/application/_card.html.haml b/app/views/application/_card.html.haml index 909d9ff81..40b3e0fce 100644 --- a/app/views/application/_card.html.haml +++ b/app/views/application/_card.html.haml @@ -5,7 +5,7 @@ .card__img = image_tag account.header.url, alt: '' .card__bar - .avatar + .avatar{ account_cat_params(account) } = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' .display-name diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml index 56c151fb3..0ebfaef93 100644 --- a/app/views/directories/index.html.haml +++ b/app/views/directories/index.html.haml @@ -24,7 +24,7 @@ = image_tag account.header.url, alt: '' .directory__card__bar = link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do - .avatar + .avatar{ account_cat_params(account) } = image_tag account.avatar.url, alt: '', class: 'u-photo' .display-name diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index d1b27fa72..d3dc0093b 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -33,6 +33,9 @@ - if Setting.custom_css.present? = stylesheet_link_tag custom_css_path, host: request.host, media: 'all' + %style{ nonce: request.content_security_policy_nonce } + != account_cat_styles + = yield :header_tags %body{ class: body_classes } diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 81dce0fd6..d93abb521 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -33,6 +33,11 @@ = f.input :setting_disable_swiping, as: :boolean, wrapper: :with_label = f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label + %h4= t 'appearance.joke' + + .fields-group + = f.input :setting_disable_joke_appearance, as: :boolean, wrapper: :with_label, fedibird_features: true + %h4= t 'appearance.toot_layout' .fields-group diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 4885878f0..ca941881c 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -55,6 +55,16 @@ %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: link_to('Mastodon', ActivityPub::TagManager.instance.url_for(@account), rel: 'me').to_str } %button{ type: :button }= t('generic.copy') + .fields-row + .fields-row__column.fields-group.fields-row__column-8 + = f.input :birthday, wrapper: :with_label, input_html: { placeholder: '2016-03-16', pattern: '\d{4}-\d{1,2}-\d{1,2}' }, hint: t('simple_form.hints.defaults.birthday'), fedibird_features: true + %p.warning-hint= t('simple_form.hints.defaults.birthday_caution') + + .fields-row + .fields-row__column.fields-group.fields-row__column-8 + = f.input :location, wrapper: :with_label, input_html: { maxlength: 50 }, hint: t('simple_form.hints.defaults.location'), fedibird_features: true + %p.warning-hint= t('simple_form.hints.defaults.location_caution') + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index 7bebfaf96..1eb23e492 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -1,7 +1,7 @@ .detailed-status.detailed-status--flex{ class: "detailed-status-#{status.visibility}" } .p-author.h-card = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do - .detailed-status__display-avatar + .detailed-status__display-avatar{ account_cat_params(status.account) } - if prefers_autoplay? = image_tag status.account.avatar_original_url, alt: '', class: 'account__avatar u-photo' - else diff --git a/app/views/statuses/_quote_status.html.haml b/app/views/statuses/_quote_status.html.haml index fa0b7b1b1..d896c8494 100644 --- a/app/views/statuses/_quote_status.html.haml +++ b/app/views/statuses/_quote_status.html.haml @@ -1,6 +1,6 @@ .status.quote-status{ dataurl: ActivityPub::TagManager.instance.url_for(status) } = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener' do - .status__avatar + .status__avatar{ account_cat_params(status.account) } %div = image_tag status.account.avatar_static_url, width: 18, height: 18, alt: '', class: 'u-photo account__avatar' %span.display-name diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index 8803f6439..f5eca7de2 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -11,12 +11,11 @@ .p-author.h-card = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener noreferrer' do - .status__avatar - %div - - if prefers_autoplay? - = image_tag status.account.avatar_original_url, alt: '', class: 'u-photo account__avatar' - - else - = image_tag status.account.avatar_static_url, alt: '', class: 'u-photo account__avatar' + .status__avatar{ account_cat_params(status.account) } + - if prefers_autoplay? + = image_tag status.account.avatar_original_url, alt: '', class: 'u-photo account__avatar' + - else + = image_tag status.account.avatar_static_url, alt: '', class: 'u-photo account__avatar' %span.display-name %bdi %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: prefers_autoplay?) diff --git a/config/locales/en.yml b/config/locales/en.yml index 4c0cf38a0..5d7fafb4e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -762,6 +762,7 @@ en: animations_and_accessibility: Animations and accessibility confirmation_dialogs: Confirmation dialogs discovery: Discovery + joke: Joke features localization: body: Mastodon is translated by volunteers. guide_link: https://crowdin.com/project/mastodon diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 5c11b9c8d..f126c8eeb 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -740,6 +740,7 @@ ja: animations_and_accessibility: アニメーションとアクセシビリティー confirmation_dialogs: 確認ダイアログ discovery: 見つける + joke: ジョーク機能 localization: body: Mastodonは有志によって翻訳されています。 guide_link: https://ja.crowdin.com/project/mastodon diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index dd26a514c..87464d745 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -31,6 +31,8 @@ en: defaults: autofollow: People who sign up through the invite will automatically follow you avatar: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px + birthday: Specify the date of birth in the format yyyy-mm-dd(ex. 2016-03-16) + birthday_caution: It will be published on the Internet. Please handle your personal information with care bot: Signal to others that the account mainly performs automated actions and might not be monitored context: One or multiple contexts where the filter should apply current_password: For security purposes please enter the password of the current account @@ -43,12 +45,15 @@ en: inbox_url: Copy the URL from the frontpage of the relay you want to use irreversible: Filtered posts will disappear irreversibly, even if filter is later removed locale: The language of the user interface, e-mails and push notifications + location: Area of primary residence or activity + location_caution: It will be published on the Internet. Please do not include detailed addresses except for business use locked: Manually control who can follow you by approving follow requests password: Use at least 8 characters phrase: Will be matched regardless of casing in text or content warning of a post scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click + setting_disable_joke_appearance: Disable April Fools' Day and other joke functions setting_display_media_default: Hide media marked as sensitive setting_display_media_hide_all: Always hide media setting_display_media_show_all: Always show media @@ -155,6 +160,7 @@ en: defaults: autofollow: Invite to follow your account avatar: Avatar + birthday: Birthday bot: This is a bot account chosen_languages: Filter languages confirm_new_password: Confirm new password @@ -172,6 +178,7 @@ en: inbox_url: URL of the relay inbox irreversible: Drop instead of hide locale: Interface language + location: Location locked: Require follow requests max_uses: Max number of uses new_password: New password @@ -188,6 +195,7 @@ en: setting_default_privacy: Posting privacy setting_default_sensitive: Always mark media as sensitive setting_delete_modal: Show confirmation dialog before deleting a post + setting_disable_joke_appearance: Disable joke feature to change appearance setting_disable_swiping: Disable swiping motions setting_display_media: Media display setting_display_media_default: Default diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 5ca748e04..a42194999 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -31,6 +31,8 @@ ja: defaults: autofollow: 招待から登録した人が自動的にあなたをフォローするようになります avatar: "%{size}までのPNG、GIF、JPGが利用可能です。%{dimensions}pxまで縮小されます" + birthday: 誕生日を年月日の順にyyyy-mm-dd(ex. 2016-03-16)という書式で指定します + birthday_caution: インターネットに公開されます。個人情報は慎重に取り扱ってください bot: このアカウントは主に自動で動作し、人が見ていない可能性があります context: フィルターを適用する対象 (複数選択可) current_password: 現在のアカウントのパスワードを入力してください @@ -43,12 +45,15 @@ ja: inbox_url: 使用したいリレーサーバーのトップページからURLをコピーします irreversible: フィルターが後で削除されても、除外された投稿は元に戻せなくなります locale: ユーザーインターフェース、メールやプッシュ通知の言語 + location: 主に居住・活動する地域・場所 + location_caution: インターネットに公開されます。ビジネス用途を除き詳細な住所を記載しないでください locked: フォロワーを手動で承認する必要があります password: 少なくとも8文字は入力してください phrase: 投稿内容の大文字小文字や閲覧注意に関係なく一致 scopes: アプリの API に許可するアクセス権を選択してください。最上位のスコープを選択する場合、個々のスコープを選択する必要はありません。 setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響) setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります + setting_disable_joke_appearance: エイプリルフール等のジョーク機能を無効にします setting_display_media_default: 閲覧注意としてマークされたメディアは隠す setting_display_media_hide_all: メディアを常に隠す setting_display_media_show_all: メディアを常に表示する @@ -155,6 +160,7 @@ ja: defaults: autofollow: 招待から参加後、あなたをフォロー avatar: アイコン + birthday: 誕生日 bot: これは BOT アカウントです chosen_languages: 表示する言語 confirm_new_password: 新しいパスワード(確認用) @@ -172,6 +178,7 @@ ja: inbox_url: リレーサーバーの inbox URL irreversible: 隠すのではなく除外する locale: 言語 + location: 場所 locked: 承認制アカウントにする max_uses: 使用できる回数 new_password: 新しいパスワード @@ -188,6 +195,7 @@ ja: setting_default_privacy: 投稿の公開範囲 setting_default_sensitive: メディアを常に閲覧注意としてマークする setting_delete_modal: 投稿を削除する前に確認ダイアログを表示する + setting_disable_joke_appearance: ジョーク機能による見た目の変更を無効にする setting_disable_swiping: スワイプでの切り替えを無効にする setting_display_media: メディアの表示 setting_display_media_default: 標準 diff --git a/config/settings.yml b/config/settings.yml index 07b654276..c45347905 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -55,6 +55,7 @@ defaults: &defaults hide_statuses_count: false hide_following_count: false hide_followers_count: false + disable_joke_appearance: false notification_emails: follow: false reblog: false diff --git a/package.json b/package.json index 193416d12..6469a2b92 100644 --- a/package.json +++ b/package.json @@ -153,6 +153,7 @@ "requestidlecallback": "^0.3.0", "reselect": "^4.0.0", "rimraf": "^3.0.2", + "s-age": "^1.1.2", "sass": "^1.37.0", "sass-loader": "^10.2.0", "stacktrace-js": "^2.0.2", diff --git a/yarn.lock b/yarn.lock index 684470a81..acfd6a2e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9869,6 +9869,11 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= +s-age@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/s-age/-/s-age-1.1.2.tgz#c0cf15233ccc93f41de92ea42c36d957977d1ea2" + integrity sha512-aSN2TlF39WLoZA/6cgYSJZhKt63kJ4EaadejPWjWY9/h4rksIqvfWY3gfd+3uAegSM1IXsA9aWeEhJtkxkFQtA== + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"