forked from Mirrors/elk
feat(status): edit history
This commit is contained in:
parent
eb3f2ab771
commit
36fc189064
11 changed files with 152 additions and 59 deletions
|
@ -8,10 +8,10 @@ defineProps<{
|
|||
}>()
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const { hide } = inject(dropdownContextKey)!
|
||||
const { hide } = inject(dropdownContextKey, undefined) || {}
|
||||
|
||||
const handleClick = (evt: MouseEvent) => {
|
||||
hide()
|
||||
hide?.()
|
||||
emit('click', evt)
|
||||
}
|
||||
</script>
|
||||
|
@ -23,7 +23,7 @@ const handleClick = (evt: MouseEvent) => {
|
|||
>
|
||||
<div v-if="icon" :class="icon" />
|
||||
<div flex="~ col">
|
||||
<div text-15px font-700>
|
||||
<div text-15px>
|
||||
<slot />
|
||||
</div>
|
||||
<div text-3 text="gray/90">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { isImagePreviewDialogOpen, isPreviewHelpOpen, isPublishDialogOpen, isSigninDialogOpen, isUserSwitcherOpen } from '~/composables/dialog'
|
||||
import { isEditHistoryDialogOpen, isImagePreviewDialogOpen, isPreviewHelpOpen, isPublishDialogOpen, isSigninDialogOpen, isUserSwitcherOpen } from '~/composables/dialog'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -18,4 +18,7 @@ import { isImagePreviewDialogOpen, isPreviewHelpOpen, isPublishDialogOpen, isSig
|
|||
<ModalDialog v-model="isImagePreviewDialogOpen">
|
||||
<img :src="imagePreview.src" :alt="imagePreview.alt" max-w-95vw max-h-95vh>
|
||||
</ModalDialog>
|
||||
<ModalDialog v-model="isEditHistoryDialogOpen">
|
||||
<StatusEditPreview :edit="statusEdit" />
|
||||
</ModalDialog>
|
||||
</template>
|
||||
|
|
|
@ -34,37 +34,7 @@ function go() {
|
|||
}
|
||||
|
||||
const createdAt = useFormattedDateTime(status.createdAt)
|
||||
const timeago = useTimeAgo(() => status.createdAt, {
|
||||
showSecond: true,
|
||||
messages: {
|
||||
justNow: 'just now',
|
||||
past: n => n,
|
||||
future: n => n.match(/\d/) ? `in ${n}` : n,
|
||||
month: (n, past) => n === 1
|
||||
? past
|
||||
? 'last month'
|
||||
: 'next month'
|
||||
: `${n}m`,
|
||||
year: (n, past) => n === 1
|
||||
? past
|
||||
? 'last year'
|
||||
: 'next year'
|
||||
: `${n}y`,
|
||||
day: (n, past) => n === 1
|
||||
? past
|
||||
? 'yesterday'
|
||||
: 'tomorrow'
|
||||
: `${n}d`,
|
||||
week: (n, past) => n === 1
|
||||
? past
|
||||
? 'last week'
|
||||
: 'next week'
|
||||
: `${n} week${n > 1 ? 's' : ''}`,
|
||||
hour: n => `${n}h`,
|
||||
minute: n => `${n}min`,
|
||||
second: n => `${n}s`,
|
||||
},
|
||||
})
|
||||
const timeago = useTimeAgo(() => status.createdAt, timeAgoOptions)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -90,7 +60,7 @@ const timeago = useTimeAgo(() => status.createdAt, {
|
|||
</time>
|
||||
</a>
|
||||
</CommonTooltip>
|
||||
<StatusEditIndicator :status="status" />
|
||||
<StatusEditIndicator :status="status" inline />
|
||||
</div>
|
||||
</div>
|
||||
<StatusReplyingTo v-if="status.inReplyToAccountId" :status="status" pt1 />
|
||||
|
|
|
@ -33,7 +33,12 @@ const visibility = $computed(() => STATUS_VISIBILITIES.find(v => v.value === sta
|
|||
<div flex="~ gap-1" items-center op50 text-sm>
|
||||
<div flex>
|
||||
<div>{{ createdAt }}</div>
|
||||
<StatusEditIndicator :status="status" />
|
||||
<StatusEditIndicator
|
||||
:status="status"
|
||||
:inline="false"
|
||||
>
|
||||
<span ml1 font-bold cursor-pointer>(Edited)</span>
|
||||
</StatusEditIndicator>
|
||||
</div>
|
||||
<div>·</div>
|
||||
<CommonTooltip :content="visibility.label" placement="bottom">
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
|
||||
const { status } = defineProps<{
|
||||
status: Status
|
||||
}>()
|
||||
|
||||
const editedAt = $computed(() => status.editedAt)
|
||||
const formatted = useFormattedDateTime(status.editedAt)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonTooltip v-if="editedAt" :content="`Edited ${formatted}`">
|
||||
<time
|
||||
:title="editedAt"
|
||||
:datetime="editedAt"
|
||||
font-bold underline decoration-dashed
|
||||
> *</time>
|
||||
</CommonTooltip>
|
||||
</template>
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{ enabled: boolean }>()
|
||||
defineSlots<'spoiler'>()
|
||||
|
||||
const [showContent, toggleContent] = $(useToggle(!props.enabled))
|
||||
</script>
|
||||
|
|
31
components/status/edit/StatusEditHistory.vue
Normal file
31
components/status/edit/StatusEditHistory.vue
Normal file
|
@ -0,0 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status, StatusEdit } from 'masto'
|
||||
|
||||
const { status } = defineProps<{
|
||||
status: Status
|
||||
}>()
|
||||
|
||||
const { data: statusEdits } = useAsyncData(`status:history:${status.id}`, () => masto.statuses.fetchHistory(status.id).then(res => res.reverse()))
|
||||
|
||||
const showHistory = (edit: StatusEdit) => {
|
||||
openEditHistoryDialog(edit)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="statusEdits">
|
||||
<CommonDropdownItem
|
||||
v-for="(edit, idx) in statusEdits"
|
||||
:key="idx"
|
||||
px="0.5"
|
||||
@click="showHistory(edit)"
|
||||
>
|
||||
{{ getDisplayName(edit.account) }}
|
||||
{{ idx === statusEdits.length - 1 ? 'created' : 'edited' }}
|
||||
{{ useTimeAgo(edit.createdAt, { showSecond: true }).value }}
|
||||
</CommonDropdownItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div i-ri:loader-2-fill animate-spin text-2xl ma />
|
||||
</template>
|
||||
</template>
|
36
components/status/edit/StatusEditIndicator.vue
Normal file
36
components/status/edit/StatusEditIndicator.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
|
||||
const { status } = defineProps<{
|
||||
status: Status
|
||||
inline: boolean
|
||||
}>()
|
||||
|
||||
const editedAt = $computed(() => status.editedAt)
|
||||
const formatted = useFormattedDateTime(status.editedAt)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="editedAt">
|
||||
<CommonTooltip v-if="inline" :content="`Edited ${formatted}`">
|
||||
<time
|
||||
:title="editedAt"
|
||||
:datetime="editedAt"
|
||||
font-bold underline decoration-dashed
|
||||
> *</time>
|
||||
</CommonTooltip>
|
||||
|
||||
<CommonDropdown v-else>
|
||||
<slot />
|
||||
|
||||
<template #popper>
|
||||
<div text-sm p2>
|
||||
<div text-center mb1>
|
||||
Edited {{ formatted }}
|
||||
</div>
|
||||
<StatusEditHistory :status="status" />
|
||||
</div>
|
||||
</template>
|
||||
</CommonDropdown>
|
||||
</template>
|
||||
</template>
|
29
components/status/edit/StatusEditPreview.vue
Normal file
29
components/status/edit/StatusEditPreview.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script setup lang="ts">
|
||||
import type { StatusEdit } from 'masto'
|
||||
|
||||
const { edit } = defineProps<{
|
||||
edit: StatusEdit
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div px3 py-4 flex="~ col">
|
||||
<div text-center flex="~ row gap-1">
|
||||
<AccountInlineInfo :account="edit.account" />
|
||||
edited {{ useFormattedDateTime(edit.createdAt).value }}
|
||||
</div>
|
||||
|
||||
<div h1px bg="gray/20" my2 />
|
||||
|
||||
<StatusSpoiler :enabled="edit.sensitive">
|
||||
<template #spoiler>
|
||||
{{ edit.spoilerText }}
|
||||
</template>
|
||||
<StatusBody :status="edit" />
|
||||
<StatusMedia
|
||||
v-if="edit.mediaAttachments.length"
|
||||
:status="edit"
|
||||
/>
|
||||
</StatusSpoiler>
|
||||
</div>
|
||||
</template>
|
|
@ -1,7 +1,9 @@
|
|||
import type { StatusEdit } from 'masto'
|
||||
import type { Draft } from './statusDrafts'
|
||||
import { STORAGE_KEY_FIRST_VISIT, STORAGE_KEY_ZEN_MODE } from '~/constants'
|
||||
|
||||
export const imagePreview = ref({ src: '', alt: '' })
|
||||
export const statusEdit = ref<StatusEdit>()
|
||||
export const isFirstVisit = useLocalStorage(STORAGE_KEY_FIRST_VISIT, true)
|
||||
export const isZenMode = useLocalStorage(STORAGE_KEY_ZEN_MODE, false)
|
||||
export const toggleZenMode = useToggle(isZenMode)
|
||||
|
@ -10,6 +12,7 @@ export const isUserSwitcherOpen = ref(false)
|
|||
export const isSigninDialogOpen = ref(false)
|
||||
export const isPublishDialogOpen = ref(false)
|
||||
export const isImagePreviewDialogOpen = ref(false)
|
||||
export const isEditHistoryDialogOpen = ref(false)
|
||||
export const isPreviewHelpOpen = ref(isFirstVisit.value)
|
||||
|
||||
export function openUserSwitcher() {
|
||||
|
@ -38,6 +41,11 @@ export function openImagePreviewDialog(image: { src: string; alt: string }) {
|
|||
isImagePreviewDialogOpen.value = true
|
||||
}
|
||||
|
||||
export function openEditHistoryDialog(edit: StatusEdit) {
|
||||
statusEdit.value = edit
|
||||
isEditHistoryDialogOpen.value = true
|
||||
}
|
||||
|
||||
export function openPreviewHelp() {
|
||||
isPreviewHelpOpen.value = true
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { MaybeRef } from '@vueuse/core'
|
||||
import type { MaybeRef, UseTimeAgoOptions } from '@vueuse/core'
|
||||
|
||||
export const useFormattedDateTime = (
|
||||
value: MaybeRef<string | Date | undefined>,
|
||||
|
@ -10,3 +10,35 @@ export const useFormattedDateTime = (
|
|||
return v ? formatter.format(new Date(v)) : ''
|
||||
})
|
||||
}
|
||||
|
||||
export const timeAgoOptions: UseTimeAgoOptions<false> = {
|
||||
showSecond: true,
|
||||
messages: {
|
||||
justNow: 'just now',
|
||||
past: n => n,
|
||||
future: n => n.match(/\d/) ? `in ${n}` : n,
|
||||
month: (n, past) => n === 1
|
||||
? past
|
||||
? 'last month'
|
||||
: 'next month'
|
||||
: `${n}m`,
|
||||
year: (n, past) => n === 1
|
||||
? past
|
||||
? 'last year'
|
||||
: 'next year'
|
||||
: `${n}y`,
|
||||
day: (n, past) => n === 1
|
||||
? past
|
||||
? 'yesterday'
|
||||
: 'tomorrow'
|
||||
: `${n}d`,
|
||||
week: (n, past) => n === 1
|
||||
? past
|
||||
? 'last week'
|
||||
: 'next week'
|
||||
: `${n} week${n > 1 ? 's' : ''}`,
|
||||
hour: n => `${n}h`,
|
||||
minute: n => `${n}min`,
|
||||
second: n => `${n}s`,
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue