Add alt and title for voice over
This commit is contained in:
parent
30ade1aab6
commit
64133a5416
|
@ -6,6 +6,7 @@
|
|||
"public": "Public",
|
||||
"search": "Search",
|
||||
"status": {
|
||||
"avatar": "Aavtar of {user}",
|
||||
"boosted": "{user} boosted",
|
||||
"poll": {
|
||||
"refresh": "Refresh",
|
||||
|
@ -16,8 +17,17 @@
|
|||
"show_more": "Show more",
|
||||
"show_less": "Show less",
|
||||
"cw": "Media hidden",
|
||||
"hide_media": "Hide media",
|
||||
"report": "Report {user}",
|
||||
"open_original": "Open original page"
|
||||
"open_original": "Open original page",
|
||||
"actions": {
|
||||
"reply": "Reply",
|
||||
"reblog": "reblog",
|
||||
"favourite": "Favourite",
|
||||
"bookmark": "Bookmark",
|
||||
"emoji_reaction": "Emoji reaction",
|
||||
"detail": "Details"
|
||||
}
|
||||
},
|
||||
"mark_as_read": "Mark as read"
|
||||
},
|
||||
|
@ -85,6 +95,14 @@
|
|||
"spoiler": {
|
||||
"placeholder": "Write your warning here"
|
||||
},
|
||||
"emoji": "Add emoji",
|
||||
"actions": {
|
||||
"attachment": "Add attachment",
|
||||
"poll": "Add poll",
|
||||
"visibility": "Change visibility",
|
||||
"cw": "Set content warning",
|
||||
"post": "Post"
|
||||
},
|
||||
"visibility": {
|
||||
"public": "Public",
|
||||
"unlisted": "Unlisted",
|
||||
|
@ -120,6 +138,10 @@
|
|||
},
|
||||
"failed_mark": "Failed to mark as read"
|
||||
},
|
||||
"detail": {
|
||||
"back": "Back",
|
||||
"close": "Close"
|
||||
},
|
||||
"profile": {
|
||||
"follow": "Follow",
|
||||
"unfollow": "Unfollow",
|
||||
|
@ -132,7 +154,10 @@
|
|||
"unblock_domain": "Unblock domain {server}",
|
||||
"timeline": "Timeline",
|
||||
"followers": "Followers",
|
||||
"followings": "Followings"
|
||||
"followings": "Followings",
|
||||
"header": "Header image of {user}",
|
||||
"avatar": "Avatar of {user}",
|
||||
"detail": "Details"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
|
|
|
@ -251,9 +251,9 @@ export default function Compose(props: Props) {
|
|||
/>
|
||||
<Popover open={popoverEmoji} handler={setPopoverEmoji}>
|
||||
<PopoverHandler>
|
||||
<span className="absolute top-1 right-1 text-gray-600 cursor-pointer">
|
||||
<button className="absolute top-1 right-1 text-gray-600 cursor-pointer" title={formatMessage({ id: 'compose.emoji' })}>
|
||||
<FaFaceLaughBeam />
|
||||
</span>
|
||||
</button>
|
||||
</PopoverHandler>
|
||||
<PopoverContent>
|
||||
<Picker data={data} onEmojiSelect={onEmojiSelect} previewPosition="none" set="native" perLine="7" theme="light" />
|
||||
|
@ -285,14 +285,26 @@ export default function Compose(props: Props) {
|
|||
<div className="w-full flex justify-between mt-1 items-center h-5">
|
||||
<div className="ml-1 flex gap-3">
|
||||
<input type="file" id="file" className="hidden" ref={uploaderRef} onChange={fileChanged} />
|
||||
<IconButton variant="text" size="sm" onClick={selectFile} className="text-gray-400 hover:text-gray-600 text-base">
|
||||
<IconButton
|
||||
variant="text"
|
||||
size="sm"
|
||||
onClick={selectFile}
|
||||
className="text-gray-400 hover:text-gray-600 text-base"
|
||||
title={formatMessage({ id: 'compose.actions.attachment' })}
|
||||
>
|
||||
<FaPaperclip />
|
||||
</IconButton>
|
||||
<IconButton variant="text" size="sm" onClick={togglePoll} className="text-gray-400 hover:text-gray-600 text-base">
|
||||
<IconButton
|
||||
variant="text"
|
||||
size="sm"
|
||||
onClick={togglePoll}
|
||||
className="text-gray-400 hover:text-gray-600 text-base"
|
||||
title={formatMessage({ id: 'compose.actions.poll' })}
|
||||
>
|
||||
<FaListCheck />
|
||||
</IconButton>
|
||||
<Popover open={popoverVisibility} handler={setPopoverVisibility}>
|
||||
<PopoverHandler>{visibilityIcon(visibility)}</PopoverHandler>
|
||||
<PopoverHandler>{visibilityIcon(visibility, formatMessage({ id: 'compose.actions.visibility' }))}</PopoverHandler>
|
||||
<PopoverContent>
|
||||
<List>
|
||||
<ListItem
|
||||
|
@ -346,6 +358,7 @@ export default function Compose(props: Props) {
|
|||
size="sm"
|
||||
className="text-gray-400 hover:text-gray-600 leading-4 text-base"
|
||||
onClick={() => setCW(true)}
|
||||
title={formatMessage({ id: 'compose.actions.cw' })}
|
||||
>
|
||||
CW
|
||||
</IconButton>
|
||||
|
@ -353,7 +366,7 @@ export default function Compose(props: Props) {
|
|||
</div>
|
||||
<div className="mr-1 flex items-center gap-2">
|
||||
<span className="text-gray-400">{remaining}</span>
|
||||
<IconButton disabled={loading} onClick={post} variant="text" size="sm">
|
||||
<IconButton disabled={loading} onClick={post} variant="text" size="sm" title={formatMessage({ id: 'compose.actions.post' })}>
|
||||
<FaPaperPlane className="text-base text-gray-600" />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
@ -363,29 +376,29 @@ export default function Compose(props: Props) {
|
|||
)
|
||||
}
|
||||
|
||||
const visibilityIcon = (visibility: 'public' | 'unlisted' | 'private' | 'direct') => {
|
||||
const visibilityIcon = (visibility: 'public' | 'unlisted' | 'private' | 'direct', title: string) => {
|
||||
switch (visibility) {
|
||||
case 'public':
|
||||
return (
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base">
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base" title={title}>
|
||||
<FaGlobe />
|
||||
</IconButton>
|
||||
)
|
||||
case 'unlisted':
|
||||
return (
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base">
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base" title={title}>
|
||||
<FaLockOpen />
|
||||
</IconButton>
|
||||
)
|
||||
case 'private':
|
||||
return (
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base">
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base" title={title}>
|
||||
<FaLock />
|
||||
</IconButton>
|
||||
)
|
||||
case 'direct':
|
||||
return (
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base">
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base" title={title}>
|
||||
<FaEnvelope />
|
||||
</IconButton>
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ import Reply from './Reply'
|
|||
import Profile from './Profile'
|
||||
import Tag from './Tag'
|
||||
import { Account } from '@/db'
|
||||
import { useIntl } from 'react-intl'
|
||||
|
||||
type Props = {
|
||||
client: MegalodonInterface
|
||||
|
@ -17,6 +18,7 @@ type Props = {
|
|||
export default function Detail(props: Props) {
|
||||
const [target, setTarget] = useState<'status' | 'reply' | 'profile' | 'tag' | null>(null)
|
||||
const router = useRouter()
|
||||
const { formatMessage } = useIntl()
|
||||
|
||||
useEffect(() => {
|
||||
if (router.query.status_id) {
|
||||
|
@ -44,13 +46,17 @@ export default function Detail(props: Props) {
|
|||
<>
|
||||
{target && (
|
||||
<div className={`bg-white ${props.className}`} style={props.style}>
|
||||
<div className="bg-blue-900 text-gray-200 flex justify-between p-2 items-center" style={{ height: '50px' }}>
|
||||
<div className="bg-blue-900 text-gray-200 flex justify-between p-2 items-center" style={{ height: '44px' }}>
|
||||
<div className="flex gap-4 items-center">
|
||||
<FaChevronLeft onClick={back} className="cursor-pointer text-lg" />
|
||||
<button className="text-lg" title={formatMessage({ id: 'detail.back' })}>
|
||||
<FaChevronLeft onClick={back} />
|
||||
</button>
|
||||
{target === 'tag' && `#${router.query.tag}`}
|
||||
</div>
|
||||
<div>
|
||||
<FaX onClick={close} className="cursor-pointer text-lg" />
|
||||
<div className="flex items-center">
|
||||
<button className="text-lg" title={formatMessage({ id: 'detail.close' })}>
|
||||
<FaX onClick={close} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{target === 'status' && (
|
||||
|
|
|
@ -2,7 +2,7 @@ import emojify from '@/utils/emojify'
|
|||
import { Entity, MegalodonInterface } from 'megalodon'
|
||||
import { MouseEventHandler, useEffect, useState } from 'react'
|
||||
import { FaEllipsisVertical } from 'react-icons/fa6'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import Timeline from './profile/Timeline'
|
||||
import Followings from './profile/Followings'
|
||||
import Followers from './profile/Followers'
|
||||
|
@ -37,6 +37,7 @@ export default function Profile(props: Props) {
|
|||
const [relationship, setRelationship] = useState<Entity.Relationship | null>(null)
|
||||
const [popoverDetail, setPopoverDetail] = useState(false)
|
||||
const [domain, setDomain] = useState<string | null>(null)
|
||||
const { formatMessage } = useIntl()
|
||||
|
||||
useEffect(() => {
|
||||
const f = async () => {
|
||||
|
@ -125,11 +126,20 @@ export default function Profile(props: Props) {
|
|||
{user && relationship && (
|
||||
<>
|
||||
<div className="header-image w-full bg-gray-100">
|
||||
<img src={user.header} alt="header image" className="w-full object-cover h-40" />
|
||||
<img
|
||||
src={user.header}
|
||||
className="w-full object-cover h-40"
|
||||
alt={formatMessage({ id: 'profile.header' }, { user: user.username })}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<div className="flex items-end justify-between" style={{ marginTop: '-50px' }}>
|
||||
<Avatar src={user.avatar} size="xl" variant="rounded" />
|
||||
<Avatar
|
||||
src={user.avatar}
|
||||
size="xl"
|
||||
variant="rounded"
|
||||
alt={formatMessage({ id: 'profile.avatar' }, { user: user.username })}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
{relationship.following ? (
|
||||
<Button color="red" onClick={() => unfollow(user.id)}>
|
||||
|
@ -142,7 +152,7 @@ export default function Profile(props: Props) {
|
|||
)}
|
||||
<Popover open={popoverDetail} handler={setPopoverDetail}>
|
||||
<PopoverHandler>
|
||||
<IconButton variant="outlined">
|
||||
<IconButton variant="outlined" title={formatMessage({ id: 'profile.detail' })}>
|
||||
<FaEllipsisVertical />
|
||||
</IconButton>
|
||||
</PopoverHandler>
|
||||
|
|
|
@ -46,6 +46,7 @@ export default function Follow(props: Props) {
|
|||
onClick={() => openUser(props.notification.account.id)}
|
||||
variant="rounded"
|
||||
style={{ width: '40px', height: '40px' }}
|
||||
alt={formatMessage({ id: 'timeline.status.avatar' }, { user: props.notification.account.username })}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ width: 'calc(100% - 56px)' }}>
|
||||
|
|
|
@ -64,6 +64,7 @@ export default function Reaction(props: Props) {
|
|||
onClick={() => openUser(status.account.id)}
|
||||
variant="rounded"
|
||||
style={{ width: '40px', height: '40px' }}
|
||||
alt={formatMessage({ id: 'timeline.status.avatar' }, { user: status.account.username })}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-gray-600 break-all overflow-hidden" style={{ width: 'calc(100% - 56px)' }}>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { FaBookmark, FaEllipsis, FaFaceLaughBeam, FaReply, FaRetweet, FaStar } f
|
|||
import Picker from '@emoji-mart/react'
|
||||
import { data } from '@/utils/emojiData'
|
||||
import { Account } from '@/db'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import { IconButton, List, ListItem, Popover, PopoverContent, PopoverHandler } from '@material-tailwind/react'
|
||||
import { useState } from 'react'
|
||||
|
||||
|
@ -19,6 +19,7 @@ export default function Actions(props: Props) {
|
|||
const [popoverDetail, setPopoverDetail] = useState(false)
|
||||
const [popoverEmoji, setPopoverEmoji] = useState(false)
|
||||
const router = useRouter()
|
||||
const { formatMessage } = useIntl()
|
||||
|
||||
const reply = async () => {
|
||||
router.push({ query: { id: router.query.id, timeline: router.query.timeline, reply_target_id: props.status.id, detail: true } })
|
||||
|
@ -69,22 +70,51 @@ export default function Actions(props: Props) {
|
|||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<IconButton variant="text" size="sm" onClick={reply} className="text-gray-400 text-base hover:text-gray-600">
|
||||
<IconButton
|
||||
variant="text"
|
||||
size="sm"
|
||||
onClick={reply}
|
||||
className="text-gray-400 text-base hover:text-gray-600"
|
||||
title={formatMessage({ id: 'timeline.status.actions.reply' })}
|
||||
>
|
||||
<FaReply className="w-4" />
|
||||
</IconButton>
|
||||
<IconButton variant="text" size="sm" onClick={reblog} className={`${retweetColor(props.status)} text-base hover:text-gray-600`}>
|
||||
<IconButton
|
||||
variant="text"
|
||||
size="sm"
|
||||
onClick={reblog}
|
||||
className={`${retweetColor(props.status)} text-base hover:text-gray-600`}
|
||||
title={formatMessage({ id: 'timeline.status.actions.reblog' })}
|
||||
>
|
||||
<FaRetweet className="w-4" />
|
||||
</IconButton>
|
||||
<IconButton variant="text" size="sm" onClick={favourite} className={`${favouriteColor(props.status)} text-base hover:text-gray-600`}>
|
||||
<IconButton
|
||||
variant="text"
|
||||
size="sm"
|
||||
onClick={favourite}
|
||||
className={`${favouriteColor(props.status)} text-base hover:text-gray-600`}
|
||||
title={formatMessage({ id: 'timeline.status.actions.favourite' })}
|
||||
>
|
||||
<FaStar className="w-4" />
|
||||
</IconButton>
|
||||
<IconButton variant="text" size="sm" onClick={bookmark} className={`${bookmarkColor(props.status)} text-base hover:text-gray-600`}>
|
||||
<IconButton
|
||||
variant="text"
|
||||
size="sm"
|
||||
onClick={bookmark}
|
||||
className={`${bookmarkColor(props.status)} text-base hover:text-gray-600`}
|
||||
title={formatMessage({ id: 'timeline.status.actions.bookmark' })}
|
||||
>
|
||||
<FaBookmark className="w-4" />
|
||||
</IconButton>
|
||||
{props.account.sns !== 'mastodon' && (
|
||||
<Popover open={popoverEmoji} handler={setPopoverEmoji}>
|
||||
<PopoverHandler>
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base">
|
||||
<IconButton
|
||||
variant="text"
|
||||
size="sm"
|
||||
className="text-gray-400 hover:text-gray-600 text-base"
|
||||
title={formatMessage({ id: 'timeline.status.actions.emoji_reaction' })}
|
||||
>
|
||||
<FaFaceLaughBeam />
|
||||
</IconButton>
|
||||
</PopoverHandler>
|
||||
|
@ -96,7 +126,12 @@ export default function Actions(props: Props) {
|
|||
|
||||
<Popover open={popoverDetail} handler={setPopoverDetail}>
|
||||
<PopoverHandler>
|
||||
<IconButton variant="text" size="sm" className="text-gray-400 hover:text-gray-600 text-base">
|
||||
<IconButton
|
||||
variant="text"
|
||||
size="sm"
|
||||
className="text-gray-400 hover:text-gray-600 text-base"
|
||||
title={formatMessage({ id: 'timeline.status.actions.detail' })}
|
||||
>
|
||||
<FaEllipsis className="w-4" />
|
||||
</IconButton>
|
||||
</PopoverHandler>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Button } from '@material-tailwind/react'
|
|||
import { Entity } from 'megalodon'
|
||||
import { useState } from 'react'
|
||||
import { FaEyeSlash } from 'react-icons/fa6'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
|
||||
type Props = {
|
||||
media: Array<Entity.Attachment>
|
||||
|
@ -11,6 +11,7 @@ type Props = {
|
|||
}
|
||||
export default function Media(props: Props) {
|
||||
const [sensitive, setSensitive] = useState(props.sensitive)
|
||||
const { formatMessage } = useIntl()
|
||||
|
||||
if (props.media.length > 0) {
|
||||
return (
|
||||
|
@ -21,7 +22,11 @@ export default function Media(props: Props) {
|
|||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<button className="absolute bg-gray-600 text-gray-200 top-1 left-1 p-1 rounded" onClick={() => setSensitive(true)}>
|
||||
<button
|
||||
className="absolute bg-gray-600 text-gray-200 top-1 left-1 p-1 rounded"
|
||||
onClick={() => setSensitive(true)}
|
||||
title={formatMessage({ id: 'timeline.status.hide_media' })}
|
||||
>
|
||||
<FaEyeSlash />
|
||||
</button>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
|
|
|
@ -5,7 +5,7 @@ import Media from './Media'
|
|||
import emojify from '@/utils/emojify'
|
||||
import Card from './Card'
|
||||
import Poll from './Poll'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import Actions from './Actions'
|
||||
import { useRouter } from 'next/router'
|
||||
import { MouseEventHandler, useState } from 'react'
|
||||
|
@ -25,6 +25,7 @@ export default function Status(props: Props) {
|
|||
const status = originalStatus(props.status)
|
||||
const [spoilered, setSpoilered] = useState(status.spoiler_text.length > 0)
|
||||
const router = useRouter()
|
||||
const { formatMessage } = useIntl()
|
||||
|
||||
const onRefresh = async () => {
|
||||
const res = await props.client.getStatus(status.id)
|
||||
|
@ -72,7 +73,15 @@ export default function Status(props: Props) {
|
|||
|
||||
return (
|
||||
<div className="border-b mr-2 py-1">
|
||||
{rebloggedHeader(props.status)}
|
||||
{rebloggedHeader(
|
||||
props.status,
|
||||
formatMessage(
|
||||
{
|
||||
id: 'timeline.status.avatar'
|
||||
},
|
||||
{ user: status.account.username }
|
||||
)
|
||||
)}
|
||||
<div className="flex">
|
||||
<div className="p-2 cursor-pointer" style={{ width: '56px' }}>
|
||||
<Avatar
|
||||
|
@ -80,6 +89,12 @@ export default function Status(props: Props) {
|
|||
onClick={() => openUser(status.account.id)}
|
||||
variant="rounded"
|
||||
style={{ width: '40px', height: '40px' }}
|
||||
alt={formatMessage(
|
||||
{
|
||||
id: 'timeline.status.avatar'
|
||||
},
|
||||
{ user: status.account.username }
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-gray-950 break-all overflow-hidden" style={{ width: 'calc(100% - 56px)' }}>
|
||||
|
@ -129,12 +144,12 @@ const originalStatus = (status: Entity.Status) => {
|
|||
}
|
||||
}
|
||||
|
||||
const rebloggedHeader = (status: Entity.Status) => {
|
||||
const rebloggedHeader = (status: Entity.Status, alt: string) => {
|
||||
if (status.reblog && !status.quote) {
|
||||
return (
|
||||
<div className="flex text-gray-600">
|
||||
<div className="grid justify-items-end pr-2" style={{ width: '56px' }}>
|
||||
<Avatar src={status.account.avatar} size="xs" variant="rounded" />
|
||||
<Avatar src={status.account.avatar} size="xs" variant="rounded" alt={alt} />
|
||||
</div>
|
||||
<div style={{ width: 'calc(100% - 56px)' }}>
|
||||
<FormattedMessage id="timeline.status.boosted" values={{ user: status.account.username }} />
|
||||
|
|
Loading…
Reference in New Issue