Add Misskey location, birthday, and isCat
This commit is contained in:
parent
1128e473c6
commit
1e6fbdea3a
41 changed files with 320 additions and 57 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
<div
|
||||
className={className}
|
||||
className={classNames('account__avatar', { 'account__avatar-inline': inline, 'account__avatar-cat': isCat })}
|
||||
data-cat-ears-color={catEarsColor}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
style={style}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
|
||||
import Button from 'mastodon/components/button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { autoPlayGif, me, isStaff, show_followed_by, follow_button_to_list_adder } from 'mastodon/initial_state';
|
||||
|
@ -14,6 +14,7 @@ import ShortNumber from 'mastodon/components/short_number';
|
|||
import { NavLink } from 'react-router-dom';
|
||||
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
||||
import AccountNoteContainer from '../containers/account_note_container';
|
||||
import age from 's-age';
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
|
@ -338,6 +339,10 @@ class Header extends ImmutablePureComponent {
|
|||
const hide_following_count = account.getIn(['other_settings', 'hide_following_count'], false);
|
||||
const hide_followers_count = account.getIn(['other_settings', 'hide_followers_count'], false);
|
||||
|
||||
const location = account.getIn(['other_settings', 'location']);
|
||||
const birthday = account.getIn(['other_settings', 'birthday']);
|
||||
const joined = account.get('created_at');
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className='account__header__image'>
|
||||
|
@ -408,7 +413,24 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
||||
|
||||
<div className='account__header__joined'><FormattedMessage id='account.joined' defaultMessage='Joined {date}' values={{ date: intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' }) }} /></div>
|
||||
<div className='account__header__personal--wrapper'>
|
||||
<table className='account__header__personal'>
|
||||
<tbody>
|
||||
{location && <tr>
|
||||
<th><Icon id='map-marker' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.location' defaultMessage='Location' /></th>
|
||||
<td>{location}</td>
|
||||
</tr>}
|
||||
{birthday && <tr>
|
||||
<th><Icon id='birthday-cake' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.birthday' defaultMessage='Birthday' /></th>
|
||||
<td><FormattedDate value={birthday} hour12={false} year='numeric' month='short' day='2-digit' />(<FormattedMessage id='account.age' defaultMessage='{age} years old}' values={{age: age(birthday)}} />)</td>
|
||||
</tr>}
|
||||
<tr>
|
||||
<th><Icon id='calendar' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.joined' defaultMessage='Joined' /></th>
|
||||
<td><FormattedDate value={joined} hour12={false} year='numeric' month='short' day='2-digit' /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!suspended && (
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "参加者",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -296,6 +296,10 @@ code {
|
|||
max-width: 50%;
|
||||
}
|
||||
|
||||
&-8 {
|
||||
max-width: 66.67%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 27px;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/, "<span class=\"quote-inline\"><br/>QT: #{link}</span>\\1")
|
||||
end
|
||||
|
||||
def nyaize_html(html)
|
||||
inside_anchor = false
|
||||
|
||||
html.split(/(<.+?>)/).compact.map do |x|
|
||||
if x.match(/^<a/)
|
||||
inside_anchor = true
|
||||
elsif x == '</a>'
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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|
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -127,6 +127,8 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
|||
:visibility_mutual,
|
||||
:visibility_limited,
|
||||
:emoji_reaction,
|
||||
:misskey_birthday,
|
||||
:misskey_location,
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
|
||||
.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
|
||||
.status__avatar{ account_cat_params(status.account) }
|
||||
- if prefers_autoplay?
|
||||
= image_tag status.account.avatar_original_url, alt: '', class: 'u-photo account__avatar'
|
||||
- else
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -740,6 +740,7 @@ ja:
|
|||
animations_and_accessibility: アニメーションとアクセシビリティー
|
||||
confirmation_dialogs: 確認ダイアログ
|
||||
discovery: 見つける
|
||||
joke: ジョーク機能
|
||||
localization:
|
||||
body: Mastodonは有志によって翻訳されています。
|
||||
guide_link: https://ja.crowdin.com/project/mastodon
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: 標準
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue