@ -0,0 +1,30 @@ | |||||
# 闭社新增配置参数说明 | |||||
## 匿名相关 | |||||
闭社新增了匿名发言的功能,开启后,以特定标记结尾(注意不要有多余的回车或空格),发言者就会变成特定的帐号。 | |||||
`ANON_TAG` 匿名标记,以这个结尾的嘟文就会成为匿名嘟文(本质只是修改了发言者) | |||||
`ANON_NAME_LIST` 匿名代号列表,一个文件名,文件内容的格式为每行一个名字。每个用户会随机取一个代号加在匿名嘟文的最前面。对应关系每日更新。 | |||||
`ANON_ACC` 匿名帐号的id。匿名嘟文的发言者会变成这个帐号。 | |||||
## 闭社树相关 | |||||
闭社设计了一套闭社树功能。 | |||||
`TREE_ADDRESS` 作为闭社树根节点的嘟文的路径(示例:`/statuses/102995298871197915`) | |||||
`TREE_ACC`建树帐号的id。跟节点发布自这个帐号的嘟文,在现实上会特殊处理。 | |||||
## 邮件提示加强 | |||||
考虑到一定会限制邮箱,闭社加强了邮件提示。 | |||||
`EMAIL_DEFAULT_DOMAIN` 默认的邮箱后缀,会显示在注册界面并自动补全 | |||||
`EMAIL_REGEX` 邮箱正则,在前端输入时检查邮箱格式是否正确,这主要是为了及时发现输错邮箱的情况,提升用户体验和减少对邮箱服务器的浪费。 | |||||
* 注意: 邮箱正则只是一个纯前端的辅助校验,与后端的实际邮箱规则无关。有些时候需要用不公开的邮箱规则创建一些机器人帐号,用于匿名功能的匿名帐号/闭社树功能的建树机器人/管理员帐号/其他bot,邮箱正则会给注册带来不便,可以直接f12打开调试界面直接删除input元素中的正则限制,也可以前期先不使用邮箱正则功能,还可以直接在服务器上通过命令行创建帐号。 | |||||
@ -0,0 +1,55 @@ | |||||
# frozen_string_literal: true | |||||
module ContextHelper | |||||
NAMED_CONTEXT_MAP = { | |||||
activitystreams: 'https://www.w3.org/ns/activitystreams', | |||||
security: 'https://w3id.org/security/v1', | |||||
}.freeze | |||||
CONTEXT_EXTENSION_MAP = { | |||||
manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' }, | |||||
sensitive: { 'sensitive' => 'as:sensitive' }, | |||||
hashtag: { 'Hashtag' => 'as:Hashtag' }, | |||||
moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } }, | |||||
also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } }, | |||||
emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' }, | |||||
featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' }, 'featuredTags' => { '@id' => 'toot:featuredTags', '@type' => '@id' } }, | |||||
property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' }, | |||||
atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' }, | |||||
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' }, | |||||
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } }, | |||||
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' }, | |||||
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' }, | |||||
discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' }, | |||||
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' }, | |||||
olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' }, | |||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, | |||||
}.freeze | |||||
def full_context | |||||
serialized_context(NAMED_CONTEXT_MAP, CONTEXT_EXTENSION_MAP) | |||||
end | |||||
def serialized_context(named_contexts_map, context_extensions_map) | |||||
context_array = [] | |||||
named_contexts = named_contexts_map.keys | |||||
context_extensions = context_extensions_map.keys | |||||
named_contexts.each do |key| | |||||
context_array << NAMED_CONTEXT_MAP[key] | |||||
end | |||||
extensions = context_extensions.each_with_object({}) do |key, h| | |||||
h.merge!(CONTEXT_EXTENSION_MAP[key]) | |||||
end | |||||
context_array << extensions unless extensions.empty? | |||||
if context_array.size == 1 | |||||
context_array.first | |||||
else | |||||
context_array | |||||
end | |||||
end | |||||
end |
@ -1 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M211.80734 139.0875c-3.18125 16.36625-28.4925 34.2775-57.5625 37.74875-15.15875 1.80875-30.08375 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.39125 27.9425 21.11625.7225 39.91875-5.20625 39.91875-5.20625l.8675 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23234 213.82 1.40609 165.31125.20859 116.09125c-.365-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67234 3.45375 78.20359.2425 107.86484 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.975 14.7525 32.975 65.0825 0 0 .41375 37.13375-4.59875 62.915" fill="#3088d4"/><path d="M177.50984 80.077v60.94125h-24.14375v-59.15c0-12.46875-5.24625-18.7975-15.74-18.7975-11.6025 0-17.4175 7.5075-17.4175 22.3525v32.37625H96.20734V85.42325c0-14.845-5.81625-22.3525-17.41875-22.3525-10.49375 0-15.74 6.32875-15.74 18.7975v59.15H38.90484V80.077c0-12.455 3.17125-22.3525 9.54125-29.675 6.56875-7.3225 15.17125-11.07625 25.85-11.07625 12.355 0 21.71125 4.74875 27.8975 14.2475l6.01375 10.08125 6.015-10.08125c6.185-9.49875 15.54125-14.2475 27.8975-14.2475 10.6775 0 19.28 3.75375 25.85 11.07625 6.36875 7.3225 9.54 17.22 9.54 29.675" fill="#fff"/></svg> | |||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.484 71.644" fill="#2ea7e0"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></svg> |
@ -1 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.41507 232.00976"><path d="M211.80683 139.0875c-3.1825 16.36625-28.4925 34.2775-57.5625 37.74875-15.16 1.80875-30.0825 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.3925 27.9425 21.115.7225 39.91625-5.20625 39.91625-5.20625l.86875 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23183 213.82 1.40558 165.31125.20808 116.09125c-.36375-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67058 3.45375 78.20308.2425 107.86433 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.97625 14.7525 32.97625 65.0825 0 0 .4125 37.13375-4.6 62.915" fill="#3088d4"/><path d="M65.68743 96.45938c0 9.01375-7.3075 16.32125-16.3225 16.32125-9.01375 0-16.32-7.3075-16.32-16.32125 0-9.01375 7.30625-16.3225 16.32-16.3225 9.015 0 16.3225 7.30875 16.3225 16.3225M124.52893 96.45938c0 9.01375-7.30875 16.32125-16.3225 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.3225 7.30875 16.3225 16.3225M183.36933 96.45938c0 9.01375-7.3075 16.32125-16.32125 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.32125 7.30875 16.32125 16.3225" fill="#fff"/></svg> | |||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.484 71.644" fill="#2ea7e0"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></svg> |
@ -1 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg"><symbol id="mastodon-svg-logo" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" /></symbol></svg> | |||||
<svg xmlns="http://www.w3.org/2000/svg"><symbol id="mastodon-svg-logo" viewBox="0 0 57.484 71.644"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></symbol></svg> |
@ -1 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" fill="#000"/></svg> | |||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.484 71.644" fill="#000"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></svg> |
@ -0,0 +1,473 @@ | |||||
import React from 'react'; | |||||
import Immutable from 'immutable'; | |||||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||||
import PropTypes from 'prop-types'; | |||||
import Avatar from './avatar'; | |||||
import AvatarOverlay from './avatar_overlay'; | |||||
import AvatarComposite from './avatar_composite'; | |||||
import RelativeTimestamp from './relative_timestamp'; | |||||
import DisplayName from './display_name'; | |||||
import StatusContent from './status_content'; | |||||
import StatusActionBar from './status_action_bar'; | |||||
import AttachmentList from './attachment_list'; | |||||
import Card from '../features/status/components/card'; | |||||
import { injectIntl, FormattedMessage } from 'react-intl'; | |||||
import ImmutablePureComponent from 'react-immutable-pure-component'; | |||||
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components'; | |||||
import { HotKeys } from 'react-hotkeys'; | |||||
import classNames from 'classnames'; | |||||
import Icon from 'mastodon/components/icon'; | |||||
import { displayMedia } from '../initial_state'; | |||||
//import DetailedStatus from '../features/status/components/detailed_status'; | |||||
// We use the component (and not the container) since we do not want | |||||
// to use the progress bar to show download progress | |||||
import Bundle from '../features/ui/components/bundle'; | |||||
export const textForScreenReader = (intl, status, rebloggedByText = false) => { | |||||
const displayName = status.getIn(['account', 'display_name']); | |||||
const values = [ | |||||
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName, | |||||
status.get('spoiler_text') && status.get('hidden') ? status.get('spoiler_text') : status.get('search_index').slice(status.get('spoiler_text').length), | |||||
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }), | |||||
status.getIn(['account', 'acct']), | |||||
]; | |||||
if (rebloggedByText) { | |||||
values.push(rebloggedByText); | |||||
} | |||||
return values.join(', '); | |||||
}; | |||||
export const defaultMediaVisibility = (status) => { | |||||
if (!status) { | |||||
return undefined; | |||||
} | |||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { | |||||
status = status.get('reblog'); | |||||
} | |||||
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all'); | |||||
}; | |||||
export default @injectIntl | |||||
class Status extends ImmutablePureComponent { | |||||
static contextTypes = { | |||||
router: PropTypes.object, | |||||
}; | |||||
static propTypes = { | |||||
status: ImmutablePropTypes.map, | |||||
account: ImmutablePropTypes.map, | |||||
otherAccounts: ImmutablePropTypes.list, | |||||
onClick: PropTypes.func, | |||||
onReply: PropTypes.func, | |||||
onFavourite: PropTypes.func, | |||||
onReblog: PropTypes.func, | |||||
onDelete: PropTypes.func, | |||||
onDirect: PropTypes.func, | |||||
onMention: PropTypes.func, | |||||
onPin: PropTypes.func, | |||||
onOpenMedia: PropTypes.func, | |||||
onOpenVideo: PropTypes.func, | |||||
onBlock: PropTypes.func, | |||||
onEmbed: PropTypes.func, | |||||
onHeightChange: PropTypes.func, | |||||
onToggleHidden: PropTypes.func, | |||||
muted: PropTypes.bool, | |||||
hidden: PropTypes.bool, | |||||
unread: PropTypes.bool, | |||||
onMoveUp: PropTypes.func, | |||||
onMoveDown: PropTypes.func, | |||||
showThread: PropTypes.bool, | |||||
getScrollPosition: PropTypes.func, | |||||
updateScrollBottom: PropTypes.func, | |||||
cacheMediaWidth: PropTypes.func, | |||||
cachedMediaWidth: PropTypes.number, | |||||
sonsIds: ImmutablePropTypes.list, | |||||
}; | |||||
// Avoid checking props that are functions (and whose equality will always | |||||
// evaluate to false. See react-immutable-pure-component for usage. | |||||
updateOnProps = [ | |||||
'status', | |||||
'account', | |||||
'muted', | |||||
'hidden', | |||||
]; | |||||
state = { | |||||
showMedia: defaultMediaVisibility(this.props.status), | |||||
statusId: undefined, | |||||
}; | |||||
// Track height changes we know about to compensate scrolling | |||||
componentDidMount () { | |||||
this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); | |||||
} | |||||
getSnapshotBeforeUpdate () { | |||||
if (this.props.getScrollPosition) { | |||||
return this.props.getScrollPosition(); | |||||
} else { | |||||
return null; | |||||
} | |||||
} | |||||
static getDerivedStateFromProps(nextProps, prevState) { | |||||
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) { | |||||
return { | |||||
showMedia: defaultMediaVisibility(nextProps.status), | |||||
statusId: nextProps.status.get('id'), | |||||
}; | |||||
} else { | |||||
return null; | |||||
} | |||||
} | |||||
// Compensate height changes | |||||
componentDidUpdate (prevProps, prevState, snapshot) { | |||||
const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); | |||||
if (doShowCard && !this.didShowCard) { | |||||
this.didShowCard = true; | |||||
if (snapshot !== null && this.props.updateScrollBottom) { | |||||
if (this.node && this.node.offsetTop < snapshot.top) { | |||||
this.props.updateScrollBottom(snapshot.height - snapshot.top); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
componentWillUnmount() { | |||||
if (this.node && this.props.getScrollPosition) { | |||||
const position = this.props.getScrollPosition(); | |||||
if (position !== null && this.node.offsetTop < position.top) { | |||||
requestAnimationFrame(() => { | |||||
this.props.updateScrollBottom(position.height - position.top); | |||||
}); | |||||
} | |||||
} | |||||
} | |||||
handleToggleMediaVisibility = () => { | |||||
this.setState({ showMedia: !this.state.showMedia }); | |||||
} | |||||
handleClick = () => { | |||||
if (this.props.onClick) { | |||||
this.props.onClick(); | |||||
return; | |||||
} | |||||
if (!this.context.router) { | |||||
return; | |||||
} | |||||
const { status } = this.props; | |||||
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); | |||||
} | |||||
handleExpandClick = (e) => { | |||||
if (this.props.onClick) { | |||||
this.props.onClick(); | |||||
return; | |||||
} | |||||
if (e.button === 0) { | |||||
if (!this.context.router) { | |||||
return; | |||||
} | |||||
const { status } = this.props; | |||||
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); | |||||
} | |||||
} | |||||
handleAccountClick = (e) => { | |||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { | |||||
const id = e.currentTarget.getAttribute('data-id'); | |||||
e.preventDefault(); | |||||
this.context.router.history.push(`/accounts/${id}`); | |||||
} | |||||
} | |||||
handleExpandedToggle = () => { | |||||
this.props.onToggleHidden(this._properStatus()); | |||||
}; | |||||
renderLoadingMediaGallery () { | |||||
return <div className='media-gallery' style={{ height: '110px' }} />; | |||||
} | |||||
renderLoadingVideoPlayer () { | |||||
return <div className='video-player' style={{ height: '110px' }} />; | |||||
} | |||||
renderLoadingAudioPlayer () { | |||||
return <div className='audio-player' style={{ height: '110px' }} />; | |||||
} | |||||
handleOpenVideo = (media, startTime) => { | |||||
this.props.onOpenVideo(media, startTime); | |||||
} | |||||
handleHotkeyReply = e => { | |||||
e.preventDefault(); | |||||
this.props.onReply(this._properStatus(), this.context.router.history); | |||||
} | |||||
handleHotkeyFavourite = () => { | |||||
this.props.onFavourite(this._properStatus()); | |||||
} | |||||
handleHotkeyBoost = e => { | |||||
this.props.onReblog(this._properStatus(), e); | |||||
} | |||||
handleHotkeyMention = e => { | |||||
e.preventDefault(); | |||||
this.props.onMention(this._properStatus().get('account'), this.context.router.history); | |||||
} | |||||
handleHotkeyOpen = () => { | |||||
this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`); | |||||
} | |||||
handleHotkeyOpenProfile = () => { | |||||
this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`); | |||||
} | |||||
handleHotkeyMoveUp = e => { | |||||
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured')); | |||||
} | |||||
handleHotkeyMoveDown = e => { | |||||
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured')); | |||||
} | |||||
handleHotkeyToggleHidden = () => { | |||||
this.props.onToggleHidden(this._properStatus()); | |||||
} | |||||
handleHotkeyToggleSensitive = () => { | |||||
this.handleToggleMediaVisibility(); | |||||
} | |||||
_properStatus () { | |||||
const { status } = this.props; | |||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { | |||||
return status.get('reblog'); | |||||
} else { | |||||
return status; | |||||
} | |||||
} | |||||
handleRef = c => { | |||||
this.node = c; | |||||
} | |||||
render () { | |||||
let media = null; | |||||
let statusAvatar, prepend, rebloggedByText; | |||||
const { intl, hidden, featured, otherAccounts, unread, showThread, sonsIds } = this.props; | |||||
let { status, account, ...other } = this.props; | |||||
if (status === null) { | |||||
return null; | |||||
} | |||||
const handlers = this.props.muted ? {} : { | |||||
reply: this.handleHotkeyReply, | |||||
favourite: this.handleHotkeyFavourite, | |||||
boost: this.handleHotkeyBoost, | |||||
mention: this.handleHotkeyMention, | |||||
open: this.handleHotkeyOpen, | |||||
openProfile: this.handleHotkeyOpenProfile, | |||||
moveUp: this.handleHotkeyMoveUp, | |||||
moveDown: this.handleHotkeyMoveDown, | |||||
toggleHidden: this.handleHotkeyToggleHidden, | |||||
toggleSensitive: this.handleHotkeyToggleSensitive, | |||||
}; | |||||
if (hidden) { | |||||
return ( | |||||
<HotKeys handlers={handlers}> | |||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex='0'> | |||||
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])} | |||||
{status.get('content')} | |||||
</div> | |||||
</HotKeys> | |||||
); | |||||
} | |||||
if (status.get('filtered') || status.getIn(['reblog', 'filtered'])) { | |||||
const minHandlers = this.props.muted ? {} : { | |||||
moveUp: this.handleHotkeyMoveUp, | |||||
moveDown: this.handleHotkeyMoveDown, | |||||
}; | |||||
return ( | |||||
<HotKeys handlers={minHandlers}> | |||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}> | |||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' /> | |||||
</div> | |||||
</HotKeys> | |||||
); | |||||
} | |||||
if (featured) { | |||||
prepend = ( | |||||
<div className='status__prepend'> | |||||
<div className='status__prepend-icon-wrapper'><Icon id='thumb-tack' className='status__prepend-icon' fixedWidth /></div> | |||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' /> | |||||
</div> | |||||
); | |||||
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { | |||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) }; | |||||
prepend = ( | |||||
<div className='status__prepend'> | |||||
<div className='status__prepend-icon-wrapper'><Icon id='retweet' className='status__prepend-icon' fixedWidth /></div> | |||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} /> | |||||
</div> | |||||
); | |||||
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: status.getIn(['account', 'acct']) }); | |||||
account = status.get('account'); | |||||
status = status.get('reblog'); | |||||
} | |||||
if (status.get('media_attachments').size > 0) { | |||||
if (this.props.muted) { | |||||
media = ( | |||||
<AttachmentList | |||||
compact | |||||
media={status.get('media_attachments')} | |||||
/> | |||||
); | |||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { | |||||
const attachment = status.getIn(['media_attachments', 0]); | |||||
media = ( | |||||
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} > | |||||
{Component => ( | |||||
<Component | |||||
src={attachment.get('url')} | |||||
alt={attachment.get('description')} | |||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)} | |||||
peaks={[0]} | |||||
height={70} | |||||
/> | |||||
)} | |||||
</Bundle> | |||||
); | |||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { | |||||
const attachment = status.getIn(['media_attachments', 0]); | |||||
media = ( | |||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} > | |||||
{Component => ( | |||||
<Component | |||||
preview={attachment.get('preview_url')} | |||||
blurhash={attachment.get('blurhash')} | |||||
src={attachment.get('url')} | |||||
alt={attachment.get('description')} | |||||
width={this.props.cachedMediaWidth} | |||||
height={110} | |||||
inline | |||||
sensitive={status.get('sensitive')} | |||||
onOpenVideo={this.handleOpenVideo} | |||||
cacheWidth={this.props.cacheMediaWidth} | |||||
visible={this.state.showMedia} | |||||
onToggleVisibility={this.handleToggleMediaVisibility} | |||||
/> | |||||
)} | |||||
</Bundle> | |||||
); | |||||
} else { | |||||
media = ( | |||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}> | |||||
{Component => ( | |||||
<Component | |||||
media={status.get('media_attachments')} | |||||
sensitive={status.get('sensitive')} | |||||
height={110} | |||||
onOpenMedia={this.props.onOpenMedia} | |||||
cacheWidth={this.props.cacheMediaWidth} | |||||
defaultWidth={this.props.cachedMediaWidth} | |||||
visible={this.state.showMedia} | |||||
onToggleVisibility={this.handleToggleMediaVisibility} | |||||
/> | |||||
)} | |||||
</Bundle> | |||||
); | |||||
} | |||||
} else if (status.get('spoiler_text').length === 0 && status.get('card')) { | |||||
media = ( | |||||
<Card | |||||
onOpenMedia={this.props.onOpenMedia} | |||||
card={status.get('card')} | |||||
compact | |||||
cacheWidth={this.props.cacheMediaWidth} | |||||
defaultWidth={this.props.cachedMediaWidth} | |||||
/> | |||||
); | |||||
} | |||||
if (otherAccounts && otherAccounts.size > 0) { | |||||
statusAvatar = <AvatarComposite accounts={otherAccounts} size={48} />; | |||||
} else if (account === undefined || account === null) { | |||||
statusAvatar = <Avatar account={status.get('account')} size={48} />; | |||||
} else { | |||||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />; | |||||
} | |||||
return ( | |||||
<HotKeys handlers={handlers}> | |||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}> | |||||
{prepend} | |||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}> | |||||
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' /> | |||||
<div className='status__info'> | |||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a> | |||||
<a onClick={this.handleAccountClick} target='_blank' data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name'> | |||||
<div className='status__avatar'> | |||||
{statusAvatar} | |||||
</div> | |||||
<DisplayName account={status.get('account')} others={otherAccounts} /> | |||||
</a> | |||||
</div> | |||||
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable /> | |||||
{media} | |||||
{showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && ( | |||||
<button className='status__content__read-more-button' onClick={this.handleClick}> | |||||
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' /> | |||||
</button> | |||||
)} | |||||
<StatusActionBar status={status} account={account} {...other} /> | |||||
</div> | |||||
</div> | |||||
</HotKeys> | |||||
); | |||||
} | |||||
} |
@ -0,0 +1,168 @@ | |||||
import { connect } from 'react-redux'; | |||||
import Status from '../components/status2'; | |||||
import { makeGetStatus } from '../selectors'; | |||||
import { | |||||
replyCompose, | |||||
mentionCompose, | |||||
directCompose, | |||||
} from '../actions/compose'; | |||||
import { | |||||
reblog, | |||||
favourite, | |||||
unreblog, | |||||
unfavourite, | |||||
pin, | |||||
unpin, | |||||
} from '../actions/interactions'; | |||||
import { | |||||
muteStatus, | |||||
unmuteStatus, | |||||
deleteStatus, | |||||
hideStatus, | |||||
revealStatus, | |||||
} from '../actions/statuses'; | |||||
import { initMuteModal } from '../actions/mutes'; | |||||
import { initBlockModal } from '../actions/blocks'; | |||||
import { initReport } from '../actions/reports'; | |||||
import { openModal } from '../actions/modal'; | |||||
import { defineMessages, injectIntl } from 'react-intl'; | |||||
import { boostModal, deleteModal } from '../initial_state'; | |||||
import { showAlertForError } from '../actions/alerts'; | |||||
const messages = defineMessages({ | |||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, | |||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, | |||||
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, | |||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' }, | |||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, | |||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, | |||||
}); | |||||
const makeMapStateToProps = () => { | |||||
const getStatus = makeGetStatus(); | |||||
const mapStateToProps = (state, props) => ({ | |||||
status: getStatus(state, props), | |||||
sonsIds: state.getIn(['contexts', 'replies', props.id]), | |||||
}); | |||||
return mapStateToProps; | |||||
}; | |||||
const mapDispatchToProps = (dispatch, { intl }) => ({ | |||||
onReply (status, router) { | |||||
dispatch((_, getState) => { | |||||
let state = getState(); | |||||
if (state.getIn(['compose', 'text']).trim().length !== 0) { | |||||
dispatch(openModal('CONFIRM', { | |||||
message: intl.formatMessage(messages.replyMessage), | |||||
confirm: intl.formatMessage(messages.replyConfirm), | |||||
onConfirm: () => dispatch(replyCompose(status, router)), | |||||
})); | |||||
} else { | |||||
dispatch(replyCompose(status, router)); | |||||
} | |||||
}); | |||||
}, | |||||
onModalReblog (status) { | |||||
if (status.get('reblogged')) { | |||||
dispatch(unreblog(status)); | |||||
} else { | |||||
dispatch(reblog(status)); | |||||
} | |||||
}, | |||||
onReblog (status, e) { | |||||
if ((e && e.shiftKey) || !boostModal) { | |||||
this.onModalReblog(status); | |||||
} else { | |||||
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); | |||||
} | |||||
}, | |||||
onFavourite (status) { | |||||
if (status.get('favourited')) { | |||||
dispatch(unfavourite(status)); | |||||
} else { | |||||
dispatch(favourite(status)); | |||||
} | |||||
}, | |||||
onPin (status) { | |||||
if (status.get('pinned')) { | |||||
dispatch(unpin(status)); | |||||
} else { | |||||
dispatch(pin(status)); | |||||
} | |||||
}, | |||||
onEmbed (status) { | |||||
dispatch(openModal('EMBED', { | |||||
url: status.get('url'), | |||||
onError: error => dispatch(showAlertForError(error)), | |||||
})); | |||||
}, | |||||
onDelete (status, history, withRedraft = false) { | |||||
if (!deleteModal) { | |||||
dispatch(deleteStatus(status.get('id'), history, withRedraft)); | |||||
} else { | |||||
dispatch(openModal('CONFIRM', { | |||||
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), | |||||
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), | |||||
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), | |||||
})); | |||||
} | |||||
}, | |||||
onDirect (account, router) { | |||||
dispatch(directCompose(account, router)); | |||||
}, | |||||
onMention (account, router) { | |||||
dispatch(mentionCompose(account, router)); | |||||
}, | |||||
onOpenMedia (media, index) { | |||||
dispatch(openModal('MEDIA', { media, index })); | |||||
}, | |||||
onOpenVideo (media, time) { | |||||
dispatch(openModal('VIDEO', { media, time })); | |||||
}, | |||||
onBlock (status) { | |||||
const account = status.get('account'); | |||||
dispatch(initBlockModal(account)); | |||||
}, | |||||
onReport (status) { | |||||
dispatch(initReport(status.get('account'), status)); | |||||
}, | |||||
onMute (account) { | |||||
dispatch(initMuteModal(account)); | |||||
}, | |||||
onMuteConversation (status) { | |||||
if (status.get('muted')) { | |||||
dispatch(unmuteStatus(status.get('id'))); | |||||
} else { | |||||
dispatch(muteStatus(status.get('id'))); | |||||
} | |||||
}, | |||||
onToggleHidden (status) { | |||||
if (status.get('hidden')) { | |||||
dispatch(revealStatus(status.get('id'))); | |||||
} else { | |||||
dispatch(hideStatus(status.get('id'))); | |||||
} | |||||
}, | |||||
}); | |||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status)); |
@ -0,0 +1,115 @@ | |||||
.column { | |||||
flex: 1 0 auto; | |||||
} | |||||
.pinned-info { | |||||
position: relative; | |||||
opacity: 0.85; | |||||
font-size: 15px; | |||||
padding: 10px 20px; | |||||
a { | |||||
color: #3fadfd; | |||||
} | |||||
} | |||||
.pinned-info__icon { | |||||
.fa { | |||||
position: absolute; | |||||
bottom: 10px; | |||||
right: 10px; | |||||
cursor: pointer; | |||||
} | |||||
} | |||||
div { | |||||
&.status__info { | |||||
& > a { | |||||
&.status__display-name { | |||||
display: inline-block; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.gifv { | |||||
& > video { | |||||
width: 100%; | |||||
max-height: 100%; | |||||
} | |||||
} | |||||
.columns-area--mobile { | |||||
.getting-started__trends { | |||||
display: block; | |||||
.trends__item { | |||||
display: flex; | |||||
} | |||||
} | |||||
} | |||||
.status__quote__wrapper { | |||||
margin-top:16px; | |||||
border-left: 5px solid #dbdbdb80; | |||||
background: #dbdbdb40; | |||||
.status { | |||||
padding-left:40px; | |||||
.status__action-bar { | |||||
display: none; | |||||
} | |||||
.status__avatar { | |||||
transform: scale(0.5); | |||||
transform-origin: 0% 0%; | |||||
} | |||||
} | |||||
} | |||||
.status__tree__quote__wrapper { | |||||
padding: 10px 5px; | |||||
background: #dbdbdb40; | |||||
cursor: pointer; | |||||
} | |||||
@keyframes like { | |||||
0% { | |||||
transform: scale(1); | |||||
} | |||||
25% { | |||||
transform: scale(1.75); | |||||
} | |||||
100% { | |||||
transform: scale(1); | |||||
} | |||||
} | |||||
@keyframes unlike { | |||||
0% { | |||||
transform: rotateY(0deg); | |||||
} | |||||
50% { | |||||
transform: rotateY(240deg); | |||||
} | |||||
80% { | |||||
transform: rotateY(140deg); | |||||
} | |||||
100% { | |||||
transform: rotateY(180deg); | |||||
} | |||||
} | |||||
.no-reduce-motion .icon-button.star-icon { | |||||
&.activate { | |||||
& > .fa-heart { | |||||
animation: like 1s linear; | |||||
} | |||||
} | |||||
} | |||||
.no-reduce-motion .icon-button.star-icon { | |||||
&.deactivate { | |||||
& > .fa-heart { | |||||
animation: unlike 1s linear; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,39 @@ | |||||
.comments-timeline { | |||||
max-height: 160px; | |||||
min-width: 60%; | |||||
max-width: 100%; | |||||
overflow: hidden; | |||||
-webkit-mask-image: linear-gradient(#1a1a1a,transparent); | |||||
mask-image: linear-gradient(#1a1a1a,transparent); | |||||
transform: scale(0.85); | |||||
transform-origin: 100% 0%; | |||||
margin-bottom: -32px; | |||||
margin-right:8px; | |||||
position: relative; | |||||
z-index:9; | |||||
&:hover { | |||||
max-height: 60vh; | |||||
overflow-y: auto; | |||||
-webkit-mask-image: none; | |||||
mask-image: none; | |||||
z-index:99; | |||||
background: $tc-background; | |||||
box-shadow: $primary-text-color 3.2px 3.2px 8px; | |||||
} | |||||
&:active { | |||||
max-height: 60vh; | |||||
overflow-y: auto; | |||||
-webkit-mask-image: none; | |||||
mask-image: none; | |||||
z-index:99; | |||||
background: $tc-background; | |||||
box-shadow: $primary-text-color 3.2px 3.2px 8px; | |||||
} | |||||
& .comments-timeline-2 { | |||||
margin-left:42px; | |||||
} | |||||
} | |||||
.comments-timeline__wrapper { | |||||
height: 135px; | |||||
} |
@ -0,0 +1,65 @@ | |||||
div.tree-ance { | |||||
.account__avatar { | |||||
display: none; | |||||
} | |||||
.display-name { | |||||
display: none; | |||||
} | |||||
} | |||||
.tree { | |||||
a.status__display-name { | |||||
>span { | |||||
>bdi { | |||||
>strong { | |||||
animation: none; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
a { | |||||
>div { | |||||
>div.account__avatar { | |||||
animation: none; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.tree-ance { | |||||
background: linear-gradient(-15deg, #282C3710,90%, #197171, 95%, #0DA454); | |||||
>div.status { | |||||
padding-left: 18px; | |||||
>div.deep__number { | |||||
text-align: left; | |||||
} | |||||
} | |||||
} | |||||
.tree-desc { | |||||
background: linear-gradient(15deg, #282C3710,90%, #197171, 95%, #0da454); | |||||
>div.status { | |||||
>div.deep__number { | |||||
text-align: right; | |||||
} | |||||
} | |||||
} | |||||
svg.tree-svg { | |||||
.node { | |||||
circle { | |||||
fill: #F3F3FF; | |||||
stroke: #2593B8; | |||||
stroke-width: 1.5px; | |||||
} | |||||
text { | |||||
font-size: 11px; | |||||
background-color: #444; | |||||
fill: #F4F4F4; | |||||
text-shadow: 0 1px 4px black; | |||||
} | |||||
cursor: pointer; | |||||
} | |||||
path.link { | |||||
fill: none; | |||||
stroke: #2593B8; | |||||
stroke-width: 1.5px; | |||||
} | |||||
} | |||||
@ -0,0 +1,3 @@ | |||||
@import 'thu/variables'; | |||||
@import 'application'; | |||||
@import 'thu/diff'; |
@ -0,0 +1,221 @@ | |||||
/* Fil */ | |||||
/* Status */ | |||||
/* Drawer */ | |||||
body { | |||||
background: rgba(73, 58, 99, 1) url(https://www.tsinghua.edu.cn/images/nav-bg.jpg) no-repeat fixed; | |||||
background-size: cover; | |||||
background-attachment: fixed; | |||||
background-position: center; | |||||
height: 100vh !important; | |||||
} | |||||
body.theme-thu { | |||||
background: rgba(73, 58, 99, 1) url(https://www.tsinghua.edu.cn/image/nav-bg.jpg) no-repeat fixed; | |||||
background-size: cover; | |||||
background-attachment: fixed; | |||||
background-position: center; | |||||
height: 100vh !important; | |||||
} | |||||
.ui { | |||||
background: rgba(0, 0, 0, .4); | |||||
} | |||||
.column { | |||||
>.scrollable { | |||||
background: rgba(128, 112, 132, 0); | |||||
border-radius: 0 0 0.25rem 0.25rem; | |||||
color: rgba(240, 240, 240, 1); | |||||
} | |||||
} | |||||
.column-back-button { | |||||
background: rgba(240, 240, 240, 1); | |||||
box-shadow: inset 0 5px 5px rgba(0, 0, 0, 0.05); | |||||
border-bottom: 1px solid transparent; | |||||
height: auto; | |||||
} | |||||
.column-header { | |||||
background: rgba(73, 58, 99, 0.4); | |||||
border-bottom: 1px solid #aaa; | |||||
border-radius: 0.25rem 0.25rem 0 0; | |||||
} | |||||
.column-icon { | |||||
background: transparent !important; | |||||
color: rgba(255, 255, 255, .5); | |||||
} | |||||
.collapsable-collapsed { | |||||
background: transparent !important; | |||||
color: rgba(255, 255, 255, .5); | |||||
} | |||||
.column-header__button { | |||||
background: transparent !important; | |||||
color: rgba(255, 255, 255, .5); | |||||
} | |||||
.column-header__back-button { | |||||
background: transparent !important; | |||||
color: rgba(255, 255, 255, .5); | |||||
} | |||||
.column-link { | |||||
background: rgba(40, 40, 40, 0); | |||||
color: rgba(200, 200, 200, 1); | |||||
box-shadow: inset 0 5px 5px rgba(0, 0, 0, 0.05); | |||||
&:hover { | |||||
background: rgba(102, 8, 116, 0.5); | |||||
color: rgba(255, 255, 255, 1); | |||||
} | |||||
} | |||||
.drawer__header { | |||||
a { | |||||
&:hover { | |||||
background: rgba(66, 40, 72, 1); | |||||
} | |||||
} | |||||
background: transparent; | |||||
} | |||||
.getting-started { | |||||
p { | |||||
color: rgba(102, 102, 102, 1); | |||||
} | |||||
background: rgb(52, 40, 62, 0.4); | |||||
} | |||||
.static-content { | |||||
p { | |||||
margin-bottom: 0.5rem; | |||||
} | |||||
} | |||||
.column-subheading { | |||||
background: rgba(255, 255, 255, 0.2); | |||||
color: white; | |||||
} | |||||
.column-header__collapsible { | |||||
>div { | |||||
background: rgba(40, 60, 85, 0.6); | |||||
border-bottom: 1px solid; | |||||
} | |||||
} | |||||
.account__moved-note__message { | |||||
color: rgba(255, 255, 255, 1); | |||||
} | |||||
.account__moved-note { | |||||
.detailed-status__display-name { | |||||
span { | |||||
color: rgba(255, 255, 255, 1); | |||||
} | |||||
strong { | |||||
color: rgba(255, 255, 255, 0.5) !important; | |||||
} | |||||
} | |||||
} | |||||
.reply-indicator__content { | |||||
color: #DDD; | |||||
a { | |||||
color: rgba(37, 136, 208, 1); | |||||
} | |||||
.status__content__spoiler-link { | |||||
background: rgba(49, 53, 67, 1); | |||||
line-height: 1.2rem; | |||||
} | |||||
} | |||||
.status__content { | |||||
color: #DDD; | |||||
a { | |||||
color: rgba(37, 136, 208, 1); | |||||
} | |||||
.status__content__spoiler-link { | |||||
background: rgba(49, 53, 67, 1); | |||||
line-height: 1.2rem; | |||||
} | |||||
} | |||||
.status__wrapper { | |||||
border-top: 1px solid #ccc; | |||||
} | |||||
.focusable { | |||||
&:focus { | |||||
background: rgba(200, 222, 243, 0.5); | |||||
} | |||||
} | |||||
.account__header { | |||||
.icon-button { | |||||
color: rgba(255, 255, 255, 1); | |||||
color: rgba(255, 255, 255, 0.8); | |||||
&:hover { | |||||
color: rgba(255, 255, 255, 1); | |||||
} | |||||
} | |||||
background: rgba(57, 48, 59, 0); | |||||
>div { | |||||
background: rgba(57, 48, 59, 0); | |||||
} | |||||
} | |||||
.icon-button { | |||||
color: rgba(255, 255, 255, 0.6); | |||||
} | |||||
.status__display-name { | |||||
strong { | |||||
color: rgba(240, 240, 240, 1); | |||||
} | |||||
} | |||||
.account__display-name { | |||||
strong { | |||||
color: rgba(240, 240, 240, 1); | |||||
} | |||||
} | |||||
.status__content__spoiler-link { | |||||
span { | |||||
color: rgba(255, 255, 255, 1); | |||||
} | |||||
} | |||||
.account__action-bar { | |||||
.icon-button { | |||||
color: rgba(255, 255, 255, 0.8); | |||||
&:hover { | |||||
color: rgba(255, 255, 255, 1); | |||||
} | |||||
} | |||||
background: rgba(255, 255, 255, 1); | |||||
border-top: 1px solid rgba(255, 255, 255, 0.25); | |||||
border-bottom: 1px solid rgba(255, 255, 255, 0.25); | |||||
} | |||||
.account__action-bar__tab { | |||||
border-left: 1px solid rgba(255, 255, 255, 0.25); | |||||
} | |||||
.notification__message { | |||||
a { | |||||
&:hover { | |||||
color: rgba(37, 136, 208, 1); | |||||
} | |||||
} | |||||
} | |||||
.detailed-status { | |||||
background: rgba(200, 222, 243, .2); | |||||
color: rgba(51, 51, 51, 1); | |||||
} | |||||
.detailed-status__display-name { | |||||
color: rgba(255, 255, 255, 0.5); | |||||
strong { | |||||
color: rgba(255, 255, 255, 0.5); | |||||
} | |||||
} | |||||
.detailed-status__meta { | |||||
color: rgba(255, 255, 255, 0.5); | |||||
} | |||||
.detailed-status__action-bar { | |||||
background: rgba(0, 0, 0, 0.05); | |||||
border-top: 1px solid #ccc; | |||||
border-bottom: 1px solid #ccc; | |||||
box-shadow: inset 0 5px 5px rgba(0, 0, 0, 0.05); | |||||
} | |||||
.drawer__inner { | |||||
background: rgb(52, 40, 62, 0.7); | |||||
border-radius: 0.25rem; | |||||
height: auto; | |||||
max-height: 100%; | |||||
overflow-y: auto; | |||||
} | |||||
.getting-started__wrapper { | |||||
background: rgb(52, 40, 62, 0.4); | |||||
} | |||||
.pinned-info { | |||||
background: rgba(73, 58, 99, 0.7); | |||||
} | |||||
.tabs-bar__wrapper { | |||||
background: rgba(23,25,31); | |||||
} |
@ -0,0 +1,8 @@ | |||||
// Dependent colors | |||||
$classic-base-color: rgba(40,44,55,0.8); | |||||
// Differences | |||||
$ui-base-color: $classic-base-color !default; | |||||
$tc-background: rgba(50,41,64,0.9) !default; | |||||
@ -0,0 +1,11 @@ | |||||
- content_for :page_title do | |||||
= @jump_url | |||||
.grid | |||||
.column-0 | |||||
.box-widget | |||||
.rich-formatting | |||||
%h2= '将前往:' | |||||
%h4= link_to @jump_url, @jump_url | |||||
.column-1 | |||||
= render 'application/sidebar' |
@ -0,0 +1,55 @@ | |||||
- content_for :page_title do | |||||
= "我的#{@year_text}" | |||||
.grid | |||||
.column-0 | |||||
.box-widget | |||||
.rich-formatting | |||||
- if @uid | |||||
= account_link_to(@account) | |||||
%h2= "#{@year_text}在闭社:" | |||||
%p | |||||
我总共发布了 | |||||
%strong | |||||
#{@total} | |||||
嘟文 | |||||
- if @total > 0 | |||||
%p | |||||
我发得最多的一天是 | |||||
%strong | |||||
#{@most_times[0][:date]} | |||||
,一下子发了 | |||||
%strong | |||||
#{@most_times[0][:num]} | |||||
条 | |||||
- if @most_fav&.favourites_count or 0 > 0 | |||||
%p | |||||
其中最高赞是“ | |||||
=link_to @most_fav.text[0..8]+'...', @most_fav.uri | |||||
”,收获了 | |||||
%strong | |||||
#{@most_fav.favourites_count} | |||||
赞 | |||||
- if @like_me_most.size > 0 | |||||
%p | |||||
给我点赞最多的是他们: | |||||
%ul | |||||
- @like_me_most.each do |a| | |||||
%li= account_link_to(a[:account], a[:num], full: a == @like_me_most.first) | |||||
- if @i_like_most.size > 0 | |||||
%p | |||||
收到我的赞最多的是他们: | |||||
%ul | |||||
- @i_like_most.each do |a| | |||||
%li= account_link_to(a[:account], a[:num], full: a == @i_like_most.first) | |||||
- if @communi_most.size > 0 | |||||
%p | |||||
和我相互交流最频繁的是: | |||||
%ul | |||||
- @communi_most.each do |a| | |||||
%li= account_link_to(a[:account], a[:num], full: a == @communi_most.first) | |||||
%br | |||||
%br | |||||
%p= '感谢陪伴,新的一年,祝平安喜乐' | |||||
.column-1 | |||||
= render 'application/sidebar' |