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 &&
+ |
+ {location} |
+
}
+ {birthday &&
+ |
+ () |
+
}
+
+ |
+ |
+
+
+
+
{!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"