fix: layout fixes for RTL languages (#591)

* fix: rtl arrows on settings page

* fix: border on settings page for RTL languages

* fix: RTL fixes for logo, search box and logout icon

* fix: RTL layout bugs in conversations

* chore: remove rtl setting icon

* improve arabic locale

* add new entries to arabic locale

* chore: include number format

* fix: RTL layout on several pages

* fix: RTL layout of account header and sign in modal

* fix: always display account handle in LTR

* fix: move character counter in publish widget to left side for RTL

* fix: remove border-ss-none unocss rule

* fix: many RTL fixes

* fix: RTL fixes for many pages

* fix: use viewer's direction in all content

* chore: use new arabic plural rules

* chore: flip arrow on main content header

* chore: fix StatusPoll and show_new_items for zh-TW

* chore: StatusPoll tooltip on bottom

* chore: add `en` variants to i18n conf

* chore: update entry to use new plural rule

* fix: automatic content direction for status

* fix: direction for account handle

* fix: direction of polls

Co-authored-by: userquin <userquin@gmail.com>
Co-authored-by: Jean-Paul Khawam <jeanpaulkhawam@protonmail.com>
Co-authored-by: Daniel Roe <daniel@roe.dev>
This commit is contained in:
Vjacheslav Trushkin 2023-01-01 16:29:11 +02:00 committed by GitHub
parent c5304be775
commit 727d05915f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 347 additions and 222 deletions

View file

@ -24,7 +24,7 @@ defineOptions({
<!-- User info -->
<div flex sm:flex-row flex-col flex-gap-2>
<div flex items-center justify-between>
<div w-17 h-17 rounded-full border-4 border-bg-base z-2 mt--2 ml--1>
<div w-17 h-17 rounded-full border-4 border-bg-base z-2 mt--2 ms--1>
<AccountAvatar :account="account" />
</div>
<a block sm:hidden href="javascript:;" @click.stop>

View file

@ -7,7 +7,7 @@
<!-- User info -->
<div flex sm:flex-row flex-col flex-gap-2>
<div flex items-center justify-between>
<div w-17 h-17 rounded-full border-4 border-bg-base z-2 mt--2 ml--1 of-hidden bg-base>
<div w-17 h-17 rounded-full border-4 border-bg-base z-2 mt--2 ms--1 of-hidden bg-base>
<div class="flex skeleton-loading-bg" w-full h-full />
</div>
<div block sm:hidden class="skeleton-loading-bg" h-8 w-30 rounded-full />

View file

@ -9,7 +9,7 @@ const serverName = $computed(() => getServerName(account))
</script>
<template>
<p line-clamp-1 whitespace-pre-wrap break-all text-secondary-light>
<p line-clamp-1 whitespace-pre-wrap break-all text-secondary-light dir="ltr">
<!-- fix: #274 only line-clamp-1 can be used here, using text-ellipsis is not valid -->
<span text-secondary>{{ getShortHandle(account) }}</span>
<span v-if="serverName" text-secondary-light>@{{ serverName }}</span>

View file

@ -90,7 +90,7 @@ const isSelf = $computed(() => currentUser.value?.account.id === account.id)
<AccountHandle :account="account" />
</div>
</div>
<div absolute top-18 right-0 flex gap-2 items-center>
<div absolute top-18 inset-ie-0 flex gap-2 items-center>
<AccountMoreButton :account="account" :command="command" />
<AccountFollowButton :account="account" :command="command" />
<!-- Edit profile -->

View file

@ -11,7 +11,7 @@ const relationship = $(useRelationship(account))
<template>
<div v-show="relationship" flex="~ col gap2" rounded min-w-90 max-w-120 z-100 overflow-hidden p-4>
<div flex="~ gap2" items-center>
<NuxtLink :to="getAccountRoute(account)" flex-auto rounded-full hover:bg-active transition-100 pr5 mr-a>
<NuxtLink :to="getAccountRoute(account)" flex-auto rounded-full hover:bg-active transition-100 pe5 me-a>
<AccountInfo :account="account" />
</NuxtLink>
<AccountFollowButton text-sm :account="account" :relationship="relationship" />

View file

@ -12,7 +12,7 @@ const { link = true, avatar = true } = defineProps<{
<AccountHoverWrapper :account="account">
<NuxtLink
:to="link ? getAccountRoute(account) : undefined"
:class="link ? 'text-link-rounded ml-0 pl-0' : ''"
:class="link ? 'text-link-rounded ms-0 ps-0' : ''"
min-w-0 flex gap-2 items-center
>
<AccountAvatar v-if="avatar" :account="account" w-5 h-5 />

View file

@ -26,7 +26,7 @@ const followersCountSR = $computed(() => forSR(props.account.followersCount))
<i18n-t keypath="account.posts_count" :plural="account.statusesCount">
<CommonTooltip v-if="statusesCountSR" :content="formatNumber(account.statusesCount)" placement="bottom">
<span aria-hidden="true" font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ statusesCount }}</span>
<span sr-only font-bold>{{ account.statusesCount }}</span>
<span sr-only font-bold>{{ formatNumber(account.statusesCount) }}</span>
</CommonTooltip>
<span v-else font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ statusesCount }}</span>
</i18n-t>
@ -41,7 +41,7 @@ const followersCountSR = $computed(() => forSR(props.account.followersCount))
<i18n-t keypath="account.following_count" :plural="account.followingCount">
<CommonTooltip v-if="followingCountSR" :content="formatNumber(account.followingCount)" placement="bottom">
<span aria-hidden="true" font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ followingCount }}</span>
<span sr-only font-bold>{{ account.followingCount }}</span>
<span sr-only font-bold>{{ formatNumber(account.followingCount) }}</span>
</CommonTooltip>
<span v-else font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ followingCount }}</span>
</i18n-t>
@ -56,7 +56,7 @@ const followersCountSR = $computed(() => forSR(props.account.followersCount))
<i18n-t keypath="account.followers_count" :plural="account.followersCount">
<CommonTooltip v-if="followersCountSR" :content="formatNumber(account.followersCount)" placement="bottom">
<span aria-hidden="true" font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ followersCount }}</span>
<span sr-only font-bold>{{ account.followersCount }}</span>
<span sr-only font-bold>{{ formatNumber(account.followersCount) }}</span>
</CommonTooltip>
<span v-else font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ followersCount }}</span>
</i18n-t>

View file

@ -23,7 +23,7 @@ const {
:data-index="index"
@click="emits('activate')"
>
<div v-if="cmd.icon" mr-2 :class="cmd.icon" />
<div v-if="cmd.icon" me-2 :class="cmd.icon" />
<div class="flex-1 flex items-baseline gap-2">
<div :class="{ 'font-medium': active }">

View file

@ -11,7 +11,7 @@ const { modelValue } = defineModel<{
<template>
<label
class="common-checkbox flex items-center cursor-pointer py-1 text-md w-full gap-y-1"
:class="hover ? 'hover:bg-active ml--2 pl-4' : null"
:class="hover ? 'hover:bg-active ms--2 ps-4' : null"
@click.prevent="modelValue = !modelValue"
>
<span
@ -23,7 +23,7 @@ const { modelValue } = defineModel<{
type="checkbox"
sr-only
>
<span ml-2 pointer-events-none>{{ label }}</span>
<span ms-2 pointer-events-none>{{ label }}</span>
</label>
</template>

View file

@ -12,7 +12,7 @@ const { modelValue } = defineModel<{
<template>
<label
class="common-radio flex items-center cursor-pointer py-1 text-md w-full gap-y-1"
:class="hover ? 'hover:bg-active ml--2 pl-4' : null"
:class="hover ? 'hover:bg-active ms--2 ps-4' : null"
@click.prevent="modelValue = value"
>
<span
@ -25,7 +25,7 @@ const { modelValue } = defineModel<{
:value="value"
sr-only
>
<span ml-2 pointer-events-none>{{ label }}</span>
<span ms-2 pointer-events-none>{{ label }}</span>
</label>
</template>

View file

@ -22,7 +22,7 @@ const useEmojis = computed(() => {
export default () => h(
'span',
{ class: 'content-rich' },
{ class: 'content-rich', dir: 'auto' },
contentToVNode(content, {
emojis: useEmojis.value,
markdown,

View file

@ -15,7 +15,7 @@ const withAccounts = $computed(() =>
<StatusCard v-if="conversation.lastStatus" :status="conversation.lastStatus" :actions="false">
<template #meta>
<div flex gap-2 text-sm text-secondary font-bold>
<p mr-1>
<p me-1>
{{ $t('conversation.with') }}
</p>
<AccountAvatar v-for="account in withAccounts" :key="account.id" h-5 w-5 :account="account" />

View file

@ -44,7 +44,7 @@ const teams: Team[] = [
<div i-ri:close-line />
</button>
<img src="/logo.svg" w-20 h-20 height="80" width="80" mxa alt="logo">
<img :alt="$t('app_logo')" src="/logo.svg" w-20 h-20 height="80" width="80" mxa class="rtl-flip">
<h1 mxa text-4xl mb4>
{{ $t('help.title') }}
</h1>

View file

@ -21,7 +21,7 @@ defineProps<{
:class="{ 'lg:hidden': backOnSmallScreen }"
@click="$router.go(-1)"
>
<div i-ri:arrow-left-line />
<div i-ri:arrow-left-line class="rtl-flip" />
</NuxtLink>
<div truncate>
<slot name="title" />

View file

@ -55,12 +55,12 @@ function onClick(e: MouseEvent) {
>
<div i-ri:close-line text-white />
</button>
<div bg="black/30" dark:bg="white/10" ml-4 my-auto text-white rounded-full flex="~ center" overflow-hidden>
<div bg="black/30" dark:bg="white/10" ms-4 my-auto text-white rounded-full flex="~ center" overflow-hidden>
<div v-if="mediaPreviewList.length > 1" p="y-1 x-2" rounded-r-0 shrink-0>
{{ mediaPreviewIndex + 1 }} / {{ mediaPreviewList.length }}
</div>
<p
v-if="current.description" bg="dark/30" dark:bg="white/10" p="y-1 x-2" rounded-r-full line-clamp-1
v-if="current.description" bg="dark/30" dark:bg="white/10" p="y-1 x-2" rounded-ie-full line-clamp-1
ws-pre-wrap break-all :title="current.description" w-full
>
{{ current.description }}

View file

@ -83,7 +83,7 @@ onBeforeUnmount(() => {
hover="bg-gray-100 dark:(bg-gray-700 text-white)"
@click="toggleDark()"
>
<span class="i-ri:sun-line dark:i-ri:moon-line flex-shrink-0 text-xl mr-4 rtl-mr-0 rtl-ml-4 !align-middle" />
<span class="i-ri:sun-line dark:i-ri:moon-line flex-shrink-0 text-xl me-4 !align-middle" />
{{ colorMode.value === 'light' ? $t('menu.toggle_theme.dark') : $t('menu.toggle_theme.light') }}
</button>
<NuxtLink
@ -94,7 +94,7 @@ onBeforeUnmount(() => {
hover="bg-gray-100 dark:(bg-gray-700 text-white)"
to="/settings"
>
<span class="i-ri:settings-2-line flex-shrink-0 text-xl mr-4 rtl-mr-0 rtl-ml-4 !align-middle" />
<span class="i-ri:settings-2-line flex-shrink-0 text-xl me-4 !align-middle" />
{{ $t('nav.settings') }}
</NuxtLink>
</div>

View file

@ -15,7 +15,7 @@ const sub = env === 'local' ? 'dev' : env === 'staging' ? 'preview' : 'alpha'
to="/"
external
>
<img :alt="$t('app_logo')" src="/logo.svg" shrink-0 aspect="1/1" sm:h-8 lg:h-10>
<img :alt="$t('app_logo')" src="/logo.svg" shrink-0 aspect="1/1" sm:h-8 lg:h-10 class="rtl-flip">
<div hidden lg:block>
{{ $t('app_name') }} <sup text-sm italic text-secondary mt-1>{{ sub }}</sup>
</div>

View file

@ -12,18 +12,14 @@ const { notification } = defineProps<{
<NuxtLink :to="getAccountRoute(notification.account)">
<div
flex items-center absolute
pl-3 pr-4 left-0 rtl-left-none
rounded-br-3
rtl="pr-3 pl-4 right-0"
rtl-rounded-bl-3
rtl-rounded-br-0
ps-3 pe-4 inset-is-0
rounded-ie-be-3
py-3 bg-base top-0
:lang="notification.status?.language ?? undefined"
:dir="notification.status?.language ? 'auto' : 'ltr'"
>
<div i-ri:user-follow-fill mr-1 color-primary />
<div i-ri:user-follow-fill me-1 color-primary />
<ContentRich
text-primary mr-1 font-bold line-clamp-1 ws-pre-wrap break-all
text-primary me-1 font-bold line-clamp-1 ws-pre-wrap break-all
:content="getDisplayName(notification.account, { rich: true })"
:emojis="notification.account.emojis"
/>
@ -34,15 +30,14 @@ const { notification } = defineProps<{
<AccountBigCard
:account="notification.account"
:lang="notification.status?.language ?? undefined"
:dir="notification.status?.language ? 'auto' : 'ltr'"
/>
</NuxtLink>
</template>
<template v-else-if="notification.type === 'admin.sign_up'">
<div flex p3 items-center bg-shaded>
<div i-ri:admin-fill mr-1 color-purple />
<div i-ri:admin-fill me-1 color-purple />
<ContentRich
text-purple mr-1 font-bold line-clamp-1 ws-pre-wrap break-all
text-purple me-1 font-bold line-clamp-1 ws-pre-wrap break-all
:content="getDisplayName(notification.account, { rich: true })"
:emojis="notification.account.emojis"
/>
@ -50,9 +45,9 @@ const { notification } = defineProps<{
</div>
</template>
<template v-else-if="notification.type === 'follow_request'">
<div flex ml-4 items-center class="-top-2.5" absolute right-2 px-2>
<div i-ri:user-follow-fill text-xl mr-1 />
<AccountInlineInfo :account="notification.account" mr1 />
<div flex ms-4 items-center class="-top-2.5" absolute inset-ie-2 px-2>
<div i-ri:user-follow-fill text-xl me-1 />
<AccountInlineInfo :account="notification.account" me1 />
</div>
<!-- TODO: accept request -->
<AccountCard :account="notification.account" />
@ -61,8 +56,8 @@ const { notification } = defineProps<{
<StatusCard :status="notification.status!" :faded="true">
<template #meta>
<div flex="~" gap-1 items-center mt1>
<div i-ri:heart-fill text-xl mr-1 color-red />
<AccountInlineInfo text-primary font-bold :account="notification.account" mr1 />
<div i-ri:heart-fill text-xl me-1 color-red />
<AccountInlineInfo text-primary font-bold :account="notification.account" me1 />
</div>
</template>
</StatusCard>
@ -71,8 +66,8 @@ const { notification } = defineProps<{
<StatusCard :status="notification.status!" :faded="true">
<template #meta>
<div flex="~" gap-1 items-center mt1>
<div i-ri:repeat-fill text-xl mr-1 color-green />
<AccountInlineInfo text-primary font-bold :account="notification.account" mr1 />
<div i-ri:repeat-fill text-xl me-1 color-green />
<AccountInlineInfo text-primary font-bold :account="notification.account" me1 />
</div>
</template>
</StatusCard>
@ -81,8 +76,8 @@ const { notification } = defineProps<{
<StatusCard :status="notification.status!" :faded="true">
<template #meta>
<div flex="~" gap-1 items-center mt1>
<div i-ri:edit-2-fill text-xl mr-1 text-secondary />
<AccountInlineInfo :account="notification.account" mr1 />
<div i-ri:edit-2-fill text-xl me-1 text-secondary />
<AccountInlineInfo :account="notification.account" me1 />
<span ws-nowrap>
{{ $t('notification.update_status') }}
</span>

View file

@ -13,15 +13,12 @@ const isExpanded = ref(false)
const lang = $computed(() => {
return count > 1 || count === 0 ? undefined : items.items[0].status?.language
})
const dir = $computed(() => {
return lang ? 'auto' : 'ltr'
})
</script>
<template>
<article flex flex-col relative :lang="lang ?? undefined" :dir="dir">
<article flex flex-col relative :lang="lang ?? undefined">
<div flex items-center top-0 left-2 pt-2 px-3>
<div i-ri:user-follow-fill mr-3 color-primary aria-hidden="true" />
<div i-ri:user-follow-fill me-3 color-primary aria-hidden="true" />
<template v-if="count > 1">
<template v-if="addSR">
<span
@ -39,11 +36,11 @@ const dir = $computed(() => {
</template>
<template v-else>
<ContentRich
text-primary mr-1 font-bold line-clamp-1 ws-pre-wrap break-all
text-primary me-1 font-bold line-clamp-1 ws-pre-wrap break-all
:content="getDisplayName(items.items[0]?.account, { rich: true })"
:emojis="items.items[0]?.account.emojis"
/>
<span mr-1 ws-nowrap>
<span me-1 ws-nowrap>
{{ $t('notification.followed_you') }}
</span>
</template>

View file

@ -12,10 +12,10 @@ const { group } = defineProps<{
<template #meta>
<div flex flex-col gap-1 mt-1>
<div v-for="like of group.likes" :key="like.account.id" flex>
<div v-if="like.reblog" i-ri:repeat-fill text-xl mr-2 color-green />
<div v-if="like.favourite && !like.reblog" i-ri:heart-fill text-xl mr-2 color-red />
<AccountInlineInfo text-primary font-bold :account="like.account" mr2 />
<div v-if="like.favourite && like.reblog" i-ri:heart-fill text-xl mr-2 color-red />
<div v-if="like.reblog" i-ri:repeat-fill text-xl me-2 color-green />
<div v-if="like.favourite && !like.reblog" i-ri:heart-fill text-xl me-2 color-red />
<AccountInlineInfo text-primary font-bold :account="like.account" me2 />
<div v-if="like.favourite && like.reblog" i-ri:heart-fill text-xl me-2 color-red />
</div>
</div>
</template>

View file

@ -99,13 +99,14 @@ function groupItems(items: Notification[]): NotificationSlot[] {
}
const { clearNotifications } = useNotifications()
const { formatNumber } = useHumanReadableNumber()
</script>
<template>
<CommonPaginator :paginator="paginator" :stream="stream" :eager="3" event-type="notification">
<template #updater="{ number, update }">
<button py-4 border="b base" flex="~ col" p-3 w-full text-primary font-bold @click="() => { update(); clearNotifications() }">
{{ $t('timeline.show_new_items', [number]) }}
{{ $t('timeline.show_new_items', number, { named: { v: formatNumber(number) } }) }}
</button>
</template>
<template #items="{ items }">

View file

@ -234,8 +234,7 @@ defineExpose({
aria-describedby="upload-failed"
flex="~ col"
gap-1 text-sm
pt-1 pl-2 pr-1 pb-2
rtl="pl-1 pr-2"
pt-1 ps-2 pe-1 pb-2
text-red-600 dark:text-red-400
border="~ base rounded red-600 dark:red-400"
>
@ -255,10 +254,10 @@ defineExpose({
</button>
</CommonTooltip>
</head>
<div v-if="isExceedingAttachmentLimit" pl-2 sm:pl-1 text-small>
<div v-if="isExceedingAttachmentLimit" ps-2 sm:ps-1 text-small>
{{ $t('state.attachments_exceed_server_limit') }}
</div>
<ol pl-2 sm:pl-1>
<ol ps-2 sm:ps-1>
<li v-for="file in failed" :key="file.name">
{{ file.name }}
</li>
@ -279,7 +278,7 @@ defineExpose({
<div flex gap-4>
<div w-12 h-full sm:block hidden />
<div
v-if="shouldExpanded" flex="~ gap-2 1" m="l--1" pt-2 justify="between" max-full
v-if="shouldExpanded" flex="~ gap-2 1" m="s--1" pt-2 justify="between" max-full
border="t base"
>
<PublishEmojiPicker
@ -308,7 +307,7 @@ defineExpose({
<div flex-auto />
<div pointer-events-none pr-1 pt-2 text-sm tabular-nums text-secondary flex gap-0.5>
<div dir="ltr" pointer-events-none pe-1 pt-2 text-sm tabular-nums text-secondary flex gap-0.5>
{{ editor?.storage.characterCount.characters() }}<span text-secondary-light>/</span><span text-secondary-light>{{ characterLimit }}</span>
</div>
@ -323,7 +322,7 @@ defineExpose({
<CommonDropdown>
<button :aria-label="$t('tooltip.change_content_visibility')" btn-action-icon w-12>
<div :class="currentVisibility.icon" />
<div i-ri:arrow-down-s-line text-sm text-secondary mr--1 />
<div i-ri:arrow-down-s-line text-sm text-secondary me--1 />
</button>
<template #popper>

View file

@ -16,6 +16,6 @@
{{ $t('pwa.dismiss') }}
</button>
</div>
<div i-ri-arrow-down-circle-line absolute text-8em bottom--10 right--10 rtl="left--10 right-unset" text-primary op10 class="-z-1" />
<div i-ri-arrow-down-circle-line absolute text-8em bottom--10 inset-ie--10 text-primary op10 class="-z-1" />
</div>
</template>

View file

@ -54,19 +54,17 @@ const activate = () => {
<template>
<div ref="el" relative px4 py2 group>
<div bg-base border="~ base" h10 rounded-full flex="~ row" items-center relative focus-within:box-shadow-outline>
<div i-ri:search-2-line mx4 absolute pointer-events-none text-secondary mt="1px" />
<div i-ri:search-2-line mx4 absolute pointer-events-none text-secondary mt="1px" class="rtl-flip" />
<input
ref="input"
v-model="query"
h-full
pl-10
rtl-pr-10
ps-10
rounded-full
w-full
bg-transparent
outline="focus:none"
pr-4
rtl-pl-4
pe-4
:placeholder="t('nav.search')"
pb="1px"
placeholder-text-secondary

View file

@ -57,7 +57,7 @@ useCommand({
</p>
</div>
</div>
<div i-ri:arrow-right-s-line rtl-i-ri:arrow-left-s-line text-xl text-secondary-light />
<div i-ri:arrow-right-s-line text-xl text-secondary-light class="rtl-flip" />
</div>
</NuxtLink>
</template>

View file

@ -94,7 +94,7 @@ async function editStatus() {
</script>
<template>
<CommonDropdown flex-none ml3 placement="bottom" :eager-mount="command">
<CommonDropdown flex-none ms3 placement="bottom" :eager-mount="command">
<StatusActionButton
:content="$t('action.more')"
color="text-purple"

View file

@ -97,14 +97,13 @@ const isDM = $computed(() => status.visibility === 'direct')
tabindex="0"
focus:outline-none focus-visible:ring="2 primary"
:lang="status.language ?? undefined"
:dir="status.language ? 'auto' : 'ltr'"
@click="onclick"
@keydown.enter="onclick"
>
<div flex justify-between>
<slot name="meta">
<div v-if="rebloggedBy && !collapseRebloggedBy" text-secondary text-sm ws-nowrap flex="~" gap-1 items-center py1 bg-base>
<div i-ri:repeat-fill mr-1 text-primary />
<div i-ri:repeat-fill me-1 text-primary />
<AccountInlineInfo font-bold :account="rebloggedBy" :avatar="!avatarOnAvatar" />
</div>
<div v-else />
@ -113,11 +112,11 @@ const isDM = $computed(() => status.visibility === 'direct')
</div>
<div flex gap-3 :class="{ 'text-secondary': faded }">
<div relative>
<div v-if="showRebloggedByAvatarOnAvatar" absolute top--3px left--0.8 rtl-left-none rtl-right--0.8 z--1 w-25px h-25px rounded-full>
<div v-if="showRebloggedByAvatarOnAvatar" absolute top--3px inset-is--0.8 z--1 w-25px h-25px rounded-full>
<AccountAvatar :account="rebloggedBy" />
</div>
<div v-else-if="collapseRebloggedBy" absolute left--0.8 rtl-left-none rtl-right--0.8 w-5.5 h-5.5 rounded-full bg-base>
<div i-ri:repeat-fill mr-1 text-primary text-sm />
<div v-else-if="collapseRebloggedBy" absolute inset-is--0.8 w-5.5 h-5.5 rounded-full bg-base>
<div i-ri:repeat-fill me-1 text-primary text-sm />
</div>
<AccountHoverWrapper :account="status.account">
<NuxtLink :to="getAccountRoute(status.account)" rounded-full>
@ -133,12 +132,12 @@ const isDM = $computed(() => status.visibility === 'direct')
<AccountHoverWrapper :account="status.account">
<StatusAccountDetails :account="status.account" />
</AccountHoverWrapper>
<div v-if="!directReply && collapseReplyingTo" flex="~" pl-1 items-center justify-center>
<div v-if="!directReply && collapseReplyingTo" flex="~" ps-1 items-center justify-center>
<StatusReplyingTo :collapsed="true" :status="status" :class="faded ? 'text-secondary-light' : ''" />
</div>
<div flex-auto />
<div v-if="!isZenMode" text-sm text-secondary flex="~ row nowrap" hover:underline>
<AccountBotIndicator v-if="status.account.bot" mr-2 />
<AccountBotIndicator v-if="status.account.bot" me-2 />
<div flex>
<CommonTooltip :content="createdAt">
<a :title="status.createdAt" :href="getStatusRoute(status).href" @click.prevent="go($event)">
@ -150,7 +149,7 @@ const isDM = $computed(() => status.visibility === 'direct')
<StatusEditIndicator :status="status" inline />
</div>
</div>
<StatusActionsMore v-if="actions !== false" :status="status" mr--2 />
<StatusActionsMore v-if="actions !== false" :status="status" me--2 />
</div>
<StatusContent :status="status" :context="context" mb2 :class="{ mt2: isDM }" />
<div>

View file

@ -23,7 +23,7 @@ const isFiltered = $computed(() => filterPhrase && (context && context !== 'deta
<div
space-y-3
:class="{
'pt2 pb0.5 px3.5 br2 border-1 rounded-3 rounded-tl-none': isDM,
'pt2 pb0.5 px3.5 border-1 rounded-3 rounded-bs-is-none': isDM,
'bg-fade border-primary-light': isDM,
}"
>

View file

@ -29,9 +29,9 @@ const isDM = $computed(() => status.visibility === 'direct')
</script>
<template>
<div :id="`status-${status.id}`" flex flex-col gap-2 pt2 pb1 px-4 relative :lang="status.language ?? undefined" dir="auto">
<StatusActionsMore :status="status" absolute right-2 top-2 />
<NuxtLink :to="getAccountRoute(status.account)" rounded-full hover:bg-active transition-100 pr5 mr-a>
<div :id="`status-${status.id}`" flex flex-col gap-2 pt2 pb1 px-4 relative :lang="status.language ?? undefined">
<StatusActionsMore :status="status" absolute inset-ie-2 top-2 />
<NuxtLink :to="getAccountRoute(status.account)" rounded-full hover:bg-active transition-100 pe5 me-a>
<AccountHoverWrapper :account="status.account">
<AccountInfo :account="status.account" />
</AccountHoverWrapper>
@ -44,7 +44,7 @@ const isDM = $computed(() => status.visibility === 'direct')
:status="status"
:inline="false"
>
<span ml1 font-bold cursor-pointer>{{ $t('state.edited') }}</span>
<span ms1 font-bold cursor-pointer>{{ $t('state.edited') }}</span>
</StatusEditIndicator>
</div>
<div>&middot;</div>

View file

@ -13,7 +13,7 @@ function toPercentage(num: number) {
const timeAgoOptions = useTimeAgoOptions()
const expiredTimeAgo = useTimeAgo(poll.expiresAt!, timeAgoOptions)
const expiredTimeFormatted = useFormattedDateTime(poll.expiresAt!)
const { formatHumanReadableNumber } = useHumanReadableNumber()
const { formatHumanReadableNumber, formatNumber, formatPercentage, forSR } = useHumanReadableNumber()
const masto = useMasto()
async function vote(e: Event) {
@ -32,10 +32,15 @@ async function vote(e: Event) {
await masto.poll.vote(poll.id, { choices })
}
const votersCount = $computed(() => poll.votersCount ?? 0)
const votersCountHR = $computed(() => formatHumanReadableNumber(votersCount))
const votersCountNumber = $computed(() => formatNumber(votersCount))
const votersCountSR = $computed(() => forSR(votersCount))
</script>
<template>
<div flex flex-col w-full items-stretch gap-3>
<div flex flex-col w-full items-stretch gap-3 dir="auto">
<form v-if="!poll.voted && !poll.expired" flex flex-col gap-4 accent-primary @click.stop="noop" @submit.prevent="vote">
<label v-for="(option, index) of poll.options" :key="index" flex items-center gap-2 px-2>
<input name="choices" :value="index" :type="poll.multiple ? 'checkbox' : 'radio'">
@ -50,17 +55,23 @@ async function vote(e: Event) {
<div flex justify-between pb-2 w-full>
<span inline-flex align-items>
{{ option.title }}
<span v-if="poll.voted && poll.ownVotes?.includes(index)" ml-2 mt-1 inline-block i-ri:checkbox-circle-line />
<span v-if="poll.voted && poll.ownVotes?.includes(index)" ms-2 mt-1 inline-block i-ri:checkbox-circle-line />
</span>
<span text-primary-active> {{ poll.votesCount ? toPercentage((option.votesCount || 0) / (poll.votesCount)) : '0%' }}</span>
<span text-primary-active> {{ formatPercentage(votersCount > 0 ? (option.votesCount || 0) / votersCount : 0) }}</span>
</div>
<div class="bg-gray/40" rounded-l-sm rounded-r-lg h-5px w-full>
<div bg-primary-active h-full class="w-[var(--bar-width)]" />
</div>
</div>
</template>
<div text-sm>
{{ $t('status.poll.count', [formatHumanReadableNumber(poll.votersCount ?? 0)]) }}
<div text-sm flex="~ inline" gap-x-1>
<i18n-t keypath="status.poll.count" :plural="votersCount">
<CommonTooltip v-if="votersCountSR" :content="votersCountNumber" placement="bottom">
<span aria-hidden="true">{{ votersCountHR }}</span>
<span sr-only>{{ votersCountNumber }}</span>
</CommonTooltip>
<span v-else>{{ votersCountNumber }}</span>
</i18n-t>
&middot;
<CommonTooltip :content="expiredTimeFormatted" class="inline-block" placement="right">
<time :datetime="poll.expiresAt!">{{ $t(poll.expired ? 'status.poll.finished' : 'status.poll.ends', [expiredTimeAgo]) }}</time>

View file

@ -102,10 +102,10 @@ const meta = $computed(() => {
<span v-else>{{ meta.user }}</span>
</a>
<a sm:text-lg :href="card.url" target="_blank">
<span v-if="meta.type === 'issue'" text-secondary-light mr-2>
<span v-if="meta.type === 'issue'" text-secondary-light me-2>
#{{ meta.number }}
</span>
<span v-if="meta.type === 'pull'" text-secondary-light mr-2>
<span v-if="meta.type === 'pull'" text-secondary-light me-2>
PR #{{ meta.number }}
</span>
<span text-secondary leading-tight>{{ meta.details }}</span>

View file

@ -11,6 +11,7 @@ const { paginator, stream } = defineProps<{
preprocess?: (items: any[]) => any[]
}>()
const { formatNumber } = useHumanReadableNumber()
const virtualScroller = $(computedEager(() => useFeatureFlags().experimentalVirtualScroll))
</script>
@ -18,7 +19,7 @@ const virtualScroller = $(computedEager(() => useFeatureFlags().experimentalVirt
<CommonPaginator v-bind="{ paginator, stream, preprocess }" :virtual-scroller="virtualScroller">
<template #updater="{ number, update }">
<button py-4 border="b base" flex="~ col" p-3 w-full text-primary font-bold @click="update">
{{ $t('timeline.show_new_items', number) }}
{{ $t('timeline.show_new_items', number, { named: { v: formatNumber(number) } }) }}
</button>
</template>
<template #default="{ item, older, newer, active }">

View file

@ -92,7 +92,7 @@ onMounted(async () => {
<template>
<form text-center justify-center items-center max-w-150 py6 flex="~ col gap-3" @submit.prevent="oauth">
<div flex="~ center" mb2>
<img src="/logo.svg" w-12 h-12 mxa height="48" width="48" alt="logo">
<img src="/logo.svg" w-12 h-12 mxa height="48" width="48" :alt="$t('app_logo')" class="rtl-flip">
<div text-3xl>
{{ $t('action.sign_in') }}
</div>
@ -102,13 +102,14 @@ onMounted(async () => {
</div>
<div :class="error ? 'animate animate-shake-x animate-delay-100' : null">
<div
dir="ltr"
flex bg-gray:10 px4 py2 mxa rounded
border="~ base" items-center font-mono
focus:outline-none focus:ring="2 primary inset"
relative
:class="displayError ? 'border-red-600 dark:border-red-400' : null"
>
<span text-secondary-light mr1>https://</span>
<span text-secondary-light me1>https://</span>
<input
ref="input"
@ -134,7 +135,7 @@ onMounted(async () => {
class="max-h-[8rem]"
>
<button
v-for="name, idx in filteredServers"
v-for="(name, idx) in filteredServers"
:id="toSelector(name)"
:key="name"
:value="name"
@ -155,7 +156,7 @@ onMounted(async () => {
</div>
</div>
<div text-secondary text-sm flex>
<div i-ri:lightbulb-line mr-1 />
<div i-ri:lightbulb-line me-1 />
<span>
<i18n-t keypath="user.tip_no_account">
<a href="https://joinmastodon.org/servers" target="_blank" hover="underline text-primary">{{ $t('user.tip_register_account') }}</a>
@ -163,7 +164,7 @@ onMounted(async () => {
</span>
</div>
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!server || busy">
<span aria-hidden="true" inline-block :class="busy ? 'i-ri:loader-2-fill animate animate-spin' : 'i-ri:login-circle-line'" />
<span aria-hidden="true" inline-block :class="busy ? 'i-ri:loader-2-fill animate animate-spin' : 'i-ri:login-circle-line'" class="rtl-flip" />
{{ $t('action.sign_in') }}
</button>
</form>

View file

@ -53,7 +53,7 @@ const switchUser = (user: UserLogin) => {
<CommonDropdownItem
v-if="isMastoInitialised && currentUser"
:text="$t('user.sign_out_account', [getFullHandle(currentUser.account)])"
icon="i-ri:logout-box-line"
icon="i-ri:logout-box-line rtl-flip"
@click="signout"
/>
</div>