Skip to content

Commit 66d13f7

Browse files
authored
Merge branch 'main' into shuuji3/feat/line-height
2 parents b24b53c + 4c63f6b commit 66d13f7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4388
-3110
lines changed

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ We've added some `UnoCSS` utilities styles to help you with that:
9090

9191
## Internationalization
9292

93-
We are using [vue-i18n](https://vue-i18n.intlify.dev/) via [nuxt-i18n](https://v8.i18n.nuxtjs.org/) to handle internationalization.
93+
We are using [vue-i18n](https://vue-i18n.intlify.dev/) via [nuxt-i18n](https://i18n.nuxtjs.org/) to handle internationalization.
9494

9595
You can check the current [translation status](https://docs.elk.zone/docs/guide/contributing#translation-status): more instructions on the table caption.
9696

components/account/AccountAvatar.vue

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
<script setup lang="ts">
22
import type { mastodon } from 'masto'
33
4-
defineProps<{
4+
const props = defineProps<{
55
account: mastodon.v1.Account
66
square?: boolean
77
}>()
88
99
const loaded = ref(false)
1010
const error = ref(false)
11+
12+
const preferredMotion = usePreferredReducedMotion()
13+
const accountAvatarSrc = computed(() => {
14+
return preferredMotion.value === 'reduce' ? (props.account?.avatarStatic ?? props.account.avatar) : props.account.avatar
15+
})
1116
</script>
1217

1318
<template>
1419
<img
15-
:key="account.avatar"
20+
:key="props.account.avatar"
1621
width="400"
1722
height="400"
1823
select-none
19-
:src="(error || !loaded) ? 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' : account.avatar"
20-
:alt="$t('account.avatar_description', [account.username])"
24+
:src="(error || !loaded) ? 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' : accountAvatarSrc"
25+
:alt="$t('account.avatar_description', [props.account.username])"
2126
loading="lazy"
2227
class="account-avatar"
23-
:class="(loaded ? 'bg-base' : 'bg-gray:10') + (square ? ' ' : ' rounded-full')"
24-
:style="{ 'clip-path': square ? `url(#avatar-mask)` : 'none' }"
28+
:class="(loaded ? 'bg-base' : 'bg-gray:10') + (props.square ? ' ' : ' rounded-full')"
29+
:style="{ 'clip-path': props.square ? `url(#avatar-mask)` : 'none' }"
2530
v-bind="$attrs"
2631
@load="loaded = true"
2732
@error="error = true"

components/common/CommonRouteTabs.vue

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ const router = useRouter()
1414
1515
useCommands(() => command
1616
? options.map(tab => ({
17-
scope: 'Tabs',
18-
name: tab.display,
19-
icon: tab.icon ?? 'i-ri:file-list-2-line',
20-
onActivate: () => router.replace(tab.to),
21-
}))
17+
scope: 'Tabs',
18+
name: tab.display,
19+
icon: tab.icon ?? 'i-ri:file-list-2-line',
20+
onActivate: () => router.replace(tab.to),
21+
}))
2222
: [])
2323
</script>
2424

components/common/CommonTabs.vue

+5-5
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ function toValidName(option: string) {
2525
2626
useCommands(() => command
2727
? tabs.value.map(tab => ({
28-
scope: 'Tabs',
28+
scope: 'Tabs',
2929
30-
name: tab.display,
31-
icon: tab.icon ?? 'i-ri:file-list-2-line',
30+
name: tab.display,
31+
icon: tab.icon ?? 'i-ri:file-list-2-line',
3232
33-
onActivate: () => modelValue.value = tab.name,
34-
}))
33+
onActivate: () => modelValue.value = tab.name,
34+
}))
3535
: [])
3636
</script>
3737

components/modal/ModalContainer.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import type { mastodon } from 'masto'
3+
import type { ConfirmDialogChoice } from '~/types'
34
import {
45
isCommandPanelOpen,
56
isConfirmDialogOpen,
@@ -13,7 +14,6 @@ import {
1314
isReportDialogOpen,
1415
isSigninDialogOpen,
1516
} from '~/composables/dialog'
16-
import type { ConfirmDialogChoice } from '~/types'
1717
1818
const isMac = useIsMac()
1919

components/nav/NavLogo.vue

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
<script setup lang="ts">
2+
</script>
3+
14
<template>
25
<span shrink-0 aspect="1/1" sm:h-8 xl:h-10 class="rtl-flip"><svg
36
xmlns="http://www.w3.org/2000/svg" w-full

components/nav/button/MoreMenu.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<script setup lang="ts">
2-
defineModel<boolean>()
2+
const model = defineModel<boolean>()
33
</script>
44

55
<template>
66
<NavBottomMoreMenu
7-
v-slot="{ toggleVisible, show }" v-model="modelValue!" flex flex-row items-center
7+
v-slot="{ toggleVisible, show }" v-model="model!" flex flex-row items-center
88
place-content-center h-full flex-1 cursor-pointer
99
>
1010
<button

components/notification/NotificationCard.vue

+39-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
<script setup lang="ts">
22
import type { mastodon } from 'masto'
33
4+
// Add undocumented 'annual_report' type introduced in v4.3
5+
// ref. https://github.com/mastodon/documentation/issues/1211#:~:text=api/v1/annual_reports
6+
type NotificationType = mastodon.v1.Notification['type'] | 'annual_report'
7+
type Notification = Omit<mastodon.v1.Notification, 'type'> & { type: NotificationType }
8+
49
const { notification } = defineProps<{
5-
notification: mastodon.v1.Notification
10+
notification: Notification
611
}>()
712
813
const { t } = useI18n()
914
15+
// list of notification types Elk currently implemented
16+
// type 'favourite' and 'reblog' should always rendered by NotificationGroupedLikes
17+
const supportedNotificationTypes: NotificationType[] = [
18+
'follow',
19+
'admin.sign_up',
20+
'admin.report',
21+
'follow_request',
22+
'update',
23+
'mention',
24+
'poll',
25+
'update',
26+
'status',
27+
'annual_report',
28+
]
29+
1030
// well-known emoji reactions types Elk does not support yet
1131
const unsupportedEmojiReactionTypes = ['pleroma:emoji_reaction', 'reaction']
12-
if (unsupportedEmojiReactionTypes.includes(notification.type))
32+
33+
if (unsupportedEmojiReactionTypes.includes(notification.type) || !supportedNotificationTypes.includes(notification.type)) {
1334
console.warn(`[DEV] ${t('notification.missing_type')} '${notification.type}' (notification.id: ${notification.id})`)
35+
}
1436
</script>
1537

1638
<template>
@@ -95,11 +117,21 @@ if (unsupportedEmojiReactionTypes.includes(notification.type))
95117
<template v-else-if="notification.type === 'mention' || notification.type === 'poll' || notification.type === 'status'">
96118
<StatusCard :status="notification.status!" />
97119
</template>
98-
<template v-else-if="!unsupportedEmojiReactionTypes.includes(notification.type)">
99-
<!-- prevent showing errors for dev for known emoji reaction types -->
100-
<!-- type 'favourite' and 'reblog' should always rendered by NotificationGroupedLikes -->
101-
<div text-red font-bold>
102-
[DEV] {{ $t('notification.missing_type') }} '{{ notification.type }}'
120+
<template v-else-if="notification.type === 'annual_report'">
121+
<div flex p4 items-center bg-shaded>
122+
<div i-mdi:party-popper text-xl me-4 color-purple />
123+
<div class="content-rich">
124+
<p>
125+
Your 2024 <NuxtLink to="/tags/Wrapstodon">
126+
#Wrapstodon
127+
</NuxtLink> awaits! Unveil your year's highlights and memorable moments on Mastodon!
128+
</p>
129+
<p>
130+
<NuxtLink :to="`https://${currentServer}/notifications`" target="_blank">
131+
View #Wrapstodon on Mastodon
132+
</NuxtLink>
133+
</p>
134+
</div>
103135
</div>
104136
</template>
105137
</article>

components/notification/NotificationPaginator.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script setup lang="ts">
22
import type { mastodon } from 'masto'
3+
import type { GroupedAccountLike, NotificationSlot } from '~/types'
34
// @ts-expect-error missing types
45
import { DynamicScrollerItem } from 'vue-virtual-scroller'
5-
import type { GroupedAccountLike, NotificationSlot } from '~/types'
66
77
const { paginator, stream } = defineProps<{
88
paginator: mastodon.Paginator<mastodon.v1.Notification[], mastodon.rest.v1.ListNotificationsParams>

components/publish/PublishWidget.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script setup lang="ts">
22
import type { mastodon } from 'masto'
3+
import type { DraftItem } from '~/types'
34
import { EditorContent } from '@tiptap/vue-3'
45
import stringLength from 'string-length'
5-
import type { DraftItem } from '~/types'
66
77
const {
88
threadComposer,
@@ -522,6 +522,7 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) {
522522
v-if="!threadIsActive || isFinalItemOfThread"
523523
btn-solid rounded-3 text-sm w-full flex="~ gap1" items-center md:w-fit class="publish-button"
524524
:aria-disabled="isPublishDisabled || isExceedingCharacterLimit" aria-describedby="publish-tooltip"
525+
:disabled="isPublishDisabled || isExceedingCharacterLimit"
525526
@click="publish"
526527
>
527528
<span v-if="isSending" block animate-spin preserve-3d>

components/publish/PublishWidgetFull.client.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
2-
import { formatTimeAgo } from '@vueuse/core'
32
import type { DraftItem } from '~/types'
3+
import { formatTimeAgo } from '@vueuse/core'
44
55
const route = useRoute()
66
const { formatNumber } = useHumanReadableNumber()

components/settings/SettingsBottomNav.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const defaultSelectedNavButtonNames = computed<NavButtonName[]>(() =>
3030
: ['explore', 'local', 'federated', 'moreMenu'],
3131
)
3232
const navButtonNamesSetting = useLocalStorage<NavButtonName[]>(STORAGE_KEY_BOTTOM_NAV_BUTTONS, defaultSelectedNavButtonNames.value)
33-
const selectedNavButtonNames = ref<NavButtonName[]>([])
33+
const selectedNavButtonNames = ref<NavButtonName[]>(navButtonNamesSetting.value)
3434
3535
const selectedNavButtons = computed<NavButton[]>(() =>
3636
selectedNavButtonNames.value.map(name =>

components/settings/SettingsThemeColors.vue

+38-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,42 @@
11
<script setup lang="ts">
22
import type { ThemeColors } from '~/composables/settings'
3+
import { THEME_COLORS } from '~/constants'
4+
5+
const themes = await import('~/constants/themes.json').then((r) => {
6+
const map = new Map<'dark' | 'light', [string, ThemeColors][]>([['dark', []], ['light', []]])
7+
const themes = r.default as [string, ThemeColors][]
8+
for (const [key, theme] of themes) {
9+
map.get('dark')!.push([key, theme])
10+
map.get('light')!.push([key, {
11+
...theme,
12+
'--c-primary': `color-mix(in srgb, ${theme['--c-primary']}, black 25%)`,
13+
}])
14+
}
15+
return map
16+
})
317
4-
const themes = await import('~/constants/themes.json').then(r => r.default) as [string, ThemeColors][]
518
const settings = useUserSettings()
619
7-
const currentTheme = computed(() => settings.value.themeColors?.['--theme-color-name'] || themes[0][1]['--theme-color-name'])
20+
const media = useMediaQuery('(prefers-color-scheme: dark)')
21+
22+
const colorMode = useColorMode()
23+
24+
const useThemes = shallowRef<[string, ThemeColors][]>([])
25+
26+
watch(() => colorMode.preference, (cm) => {
27+
const dark = cm === 'dark' || (cm === 'system' && media.value)
28+
const newThemes = dark ? themes.get('dark')! : themes.get('light')!
29+
const key = settings.value.themeColors?.['--theme-color-name'] || THEME_COLORS.defaultTheme
30+
for (const [k, theme] of newThemes) {
31+
if (k === key) {
32+
settings.value.themeColors = theme
33+
break
34+
}
35+
}
36+
useThemes.value = newThemes
37+
}, { immediate: true, flush: 'post' })
38+
39+
const currentTheme = computed(() => settings.value.themeColors?.['--theme-color-name'] || THEME_COLORS.defaultTheme)
840
941
function updateTheme(theme: ThemeColors) {
1042
settings.value.themeColors = theme
@@ -18,10 +50,11 @@ function updateTheme(theme: ThemeColors) {
1850
</h2>
1951
<div flex="~ gap4 wrap" p2 role="group" aria-labelledby="interface-tc">
2052
<button
21-
v-for="[key, theme] in themes" :key="key"
53+
v-for="[key, theme] in useThemes" :key="key"
2254
:style="{
23-
'background': key,
24-
'--local-ring-color': key,
55+
'--rgb-primary': theme['--rgb-primary'],
56+
'background': theme['--c-primary'],
57+
'--local-ring-color': theme['--c-primary'],
2558
}"
2659
type="button"
2760
:class="currentTheme === theme['--theme-color-name'] ? 'ring-2' : 'scale-90'"
+6-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
<script setup lang="ts">
2+
import type { mastodon } from 'masto'
3+
24
const paginator = useMastoClient().v1.timelines.public.list({ limit: 30, local: true })
35
const stream = useStreaming(client => client.public.local.subscribe())
6+
function reorderAndFilter(items: mastodon.v1.Status[]) {
7+
return reorderedTimeline(items, 'public')
8+
}
49
</script>
510

611
<template>
712
<div>
8-
<TimelinePaginator v-bind="{ paginator, stream }" context="public" />
13+
<TimelinePaginator v-bind="{ paginator, stream }" :preprocess="reorderAndFilter" context="public" />
914
</div>
1015
</template>

components/tiptap/TiptapEmojiList.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script setup lang="ts">
2-
import { getEmojiMatchesInText } from '@iconify/utils/lib/emoji/replace/find'
3-
import { emojiFilename, emojiPrefix, emojiRegEx } from '~~/config/emojis'
42
import type { CommandHandler } from '~/composables/command'
53
import type { CustomEmoji, Emoji } from '~/composables/tiptap/suggestion'
4+
import { getEmojiMatchesInText } from '@iconify/utils/lib/emoji/replace/find'
5+
import { emojiFilename, emojiPrefix, emojiRegEx } from '~~/config/emojis'
66
import { isCustomEmoji } from '~/composables/tiptap/suggestion'
77
88
const { items, command } = defineProps<{

composables/command.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { LocaleObject } from '@nuxtjs/i18n'
22
import type { ComputedRef } from 'vue'
3+
import type { SearchResult } from '~/composables/masto/search'
34
import Fuse from 'fuse.js'
45
import { defineStore } from 'pinia'
5-
import type { SearchResult } from '~/composables/masto/search'
66

77
// @unocss-include
88

composables/content-parse.ts

+5
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ export function parseMastodonHTML(
9090
inReplyToStatus,
9191
} = options
9292

93+
// remove newline before Tags
94+
html = html.replace(/\n(<[^>]+>)/g, (_1, raw) => {
95+
return raw
96+
})
97+
9398
if (markdown) {
9499
// Handle code blocks
95100
html = html

composables/dialog.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { mastodon } from 'masto'
2-
import { STORAGE_KEY_FIRST_VISIT } from '~/constants'
32
import type { ConfirmDialogChoice, ConfirmDialogOptions, DraftItem, ErrorDialogData } from '~/types'
3+
import { STORAGE_KEY_FIRST_VISIT } from '~/constants'
44

55
export const confirmDialogChoice = ref<ConfirmDialogChoice>()
66
export const confirmDialogLabel = ref<ConfirmDialogOptions>()

composables/masto/masto.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type { Pausable } from '@vueuse/core'
22
import type { mastodon } from 'masto'
33
import type { Ref } from 'vue'
44
import type { ElkInstance } from '../users'
5-
import { createRestAPIClient, createStreamingAPIClient } from 'masto'
65
import type { UserLogin } from '~/types'
6+
import { createRestAPIClient, createStreamingAPIClient } from 'masto'
77

88
export function createMasto() {
99
return {

composables/masto/publish.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { DraftItem } from '~~/types'
21
import type { mastodon } from 'masto'
32
import type { Ref } from 'vue'
3+
import type { DraftItem } from '~~/types'
44
import { fileOpen } from 'browser-fs-access'
55

66
export function usePublish(options: {

composables/masto/statusDrafts.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { mastodon } from 'masto'
22
import type { ComputedRef, Ref } from 'vue'
3-
import { STORAGE_KEY_DRAFTS } from '~/constants'
43
import type { DraftItem, DraftMap } from '~/types'
54
import type { Mutable } from '~/types/utils'
5+
import { STORAGE_KEY_DRAFTS } from '~/constants'
66

77
export const currentUserDrafts = (import.meta.server || process.test)
88
? computed<DraftMap>(() => ({}))

composables/users.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import type { MaybeRefOrGetter, RemovableRef } from '@vueuse/core'
22
import type { mastodon } from 'masto'
33
import type { EffectScope, Ref } from 'vue'
44
import type { ElkMasto } from './masto/masto'
5-
import { withoutProtocol } from 'ufo'
65
import type { PushNotificationPolicy, PushNotificationRequest } from '~/composables/push-notifications/types'
6+
import type { UserLogin } from '~/types'
7+
import type { Overwrite } from '~/types/utils'
8+
import { withoutProtocol } from 'ufo'
79
import {
810
DEFAULT_POST_CHARS_LIMIT,
911
STORAGE_KEY_CURRENT_USER_HANDLE,
@@ -12,8 +14,6 @@ import {
1214
STORAGE_KEY_NOTIFICATION_POLICY,
1315
STORAGE_KEY_SERVERS,
1416
} from '~/constants'
15-
import type { UserLogin } from '~/types'
16-
import type { Overwrite } from '~/types/utils'
1717

1818
const mock = process.mock
1919

0 commit comments

Comments
 (0)