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 emit = defineEmits(['click'])
|
||||||
|
|
||||||
const { hide } = inject(dropdownContextKey)!
|
const { hide } = inject(dropdownContextKey, undefined) || {}
|
||||||
|
|
||||||
const handleClick = (evt: MouseEvent) => {
|
const handleClick = (evt: MouseEvent) => {
|
||||||
hide()
|
hide?.()
|
||||||
emit('click', evt)
|
emit('click', evt)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -23,7 +23,7 @@ const handleClick = (evt: MouseEvent) => {
|
||||||
>
|
>
|
||||||
<div v-if="icon" :class="icon" />
|
<div v-if="icon" :class="icon" />
|
||||||
<div flex="~ col">
|
<div flex="~ col">
|
||||||
<div text-15px font-700>
|
<div text-15px>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<div text-3 text="gray/90">
|
<div text-3 text="gray/90">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { isImagePreviewDialogOpen, isPreviewHelpOpen, isPublishDialogOpen, isSigninDialogOpen, isUserSwitcherOpen } from '~/composables/dialog'
|
import { isEditHistoryDialogOpen, isImagePreviewDialogOpen, isPreviewHelpOpen, isPublishDialogOpen, isSigninDialogOpen, isUserSwitcherOpen } from '~/composables/dialog'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -18,4 +18,7 @@ import { isImagePreviewDialogOpen, isPreviewHelpOpen, isPublishDialogOpen, isSig
|
||||||
<ModalDialog v-model="isImagePreviewDialogOpen">
|
<ModalDialog v-model="isImagePreviewDialogOpen">
|
||||||
<img :src="imagePreview.src" :alt="imagePreview.alt" max-w-95vw max-h-95vh>
|
<img :src="imagePreview.src" :alt="imagePreview.alt" max-w-95vw max-h-95vh>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
|
<ModalDialog v-model="isEditHistoryDialogOpen">
|
||||||
|
<StatusEditPreview :edit="statusEdit" />
|
||||||
|
</ModalDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -34,37 +34,7 @@ function go() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdAt = useFormattedDateTime(status.createdAt)
|
const createdAt = useFormattedDateTime(status.createdAt)
|
||||||
const timeago = useTimeAgo(() => status.createdAt, {
|
const timeago = useTimeAgo(() => status.createdAt, timeAgoOptions)
|
||||||
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`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -90,7 +60,7 @@ const timeago = useTimeAgo(() => status.createdAt, {
|
||||||
</time>
|
</time>
|
||||||
</a>
|
</a>
|
||||||
</CommonTooltip>
|
</CommonTooltip>
|
||||||
<StatusEditIndicator :status="status" />
|
<StatusEditIndicator :status="status" inline />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<StatusReplyingTo v-if="status.inReplyToAccountId" :status="status" pt1 />
|
<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="~ gap-1" items-center op50 text-sm>
|
||||||
<div flex>
|
<div flex>
|
||||||
<div>{{ createdAt }}</div>
|
<div>{{ createdAt }}</div>
|
||||||
<StatusEditIndicator :status="status" />
|
<StatusEditIndicator
|
||||||
|
:status="status"
|
||||||
|
:inline="false"
|
||||||
|
>
|
||||||
|
<span ml1 font-bold cursor-pointer>(Edited)</span>
|
||||||
|
</StatusEditIndicator>
|
||||||
</div>
|
</div>
|
||||||
<div>·</div>
|
<div>·</div>
|
||||||
<CommonTooltip :content="visibility.label" placement="bottom">
|
<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">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{ enabled: boolean }>()
|
const props = defineProps<{ enabled: boolean }>()
|
||||||
defineSlots<'spoiler'>()
|
|
||||||
|
|
||||||
const [showContent, toggleContent] = $(useToggle(!props.enabled))
|
const [showContent, toggleContent] = $(useToggle(!props.enabled))
|
||||||
</script>
|
</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 type { Draft } from './statusDrafts'
|
||||||
import { STORAGE_KEY_FIRST_VISIT, STORAGE_KEY_ZEN_MODE } from '~/constants'
|
import { STORAGE_KEY_FIRST_VISIT, STORAGE_KEY_ZEN_MODE } from '~/constants'
|
||||||
|
|
||||||
export const imagePreview = ref({ src: '', alt: '' })
|
export const imagePreview = ref({ src: '', alt: '' })
|
||||||
|
export const statusEdit = ref<StatusEdit>()
|
||||||
export const isFirstVisit = useLocalStorage(STORAGE_KEY_FIRST_VISIT, true)
|
export const isFirstVisit = useLocalStorage(STORAGE_KEY_FIRST_VISIT, true)
|
||||||
export const isZenMode = useLocalStorage(STORAGE_KEY_ZEN_MODE, false)
|
export const isZenMode = useLocalStorage(STORAGE_KEY_ZEN_MODE, false)
|
||||||
export const toggleZenMode = useToggle(isZenMode)
|
export const toggleZenMode = useToggle(isZenMode)
|
||||||
|
@ -10,6 +12,7 @@ export const isUserSwitcherOpen = ref(false)
|
||||||
export const isSigninDialogOpen = ref(false)
|
export const isSigninDialogOpen = ref(false)
|
||||||
export const isPublishDialogOpen = ref(false)
|
export const isPublishDialogOpen = ref(false)
|
||||||
export const isImagePreviewDialogOpen = ref(false)
|
export const isImagePreviewDialogOpen = ref(false)
|
||||||
|
export const isEditHistoryDialogOpen = ref(false)
|
||||||
export const isPreviewHelpOpen = ref(isFirstVisit.value)
|
export const isPreviewHelpOpen = ref(isFirstVisit.value)
|
||||||
|
|
||||||
export function openUserSwitcher() {
|
export function openUserSwitcher() {
|
||||||
|
@ -38,6 +41,11 @@ export function openImagePreviewDialog(image: { src: string; alt: string }) {
|
||||||
isImagePreviewDialogOpen.value = true
|
isImagePreviewDialogOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openEditHistoryDialog(edit: StatusEdit) {
|
||||||
|
statusEdit.value = edit
|
||||||
|
isEditHistoryDialogOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
export function openPreviewHelp() {
|
export function openPreviewHelp() {
|
||||||
isPreviewHelpOpen.value = true
|
isPreviewHelpOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { MaybeRef } from '@vueuse/core'
|
import type { MaybeRef, UseTimeAgoOptions } from '@vueuse/core'
|
||||||
|
|
||||||
export const useFormattedDateTime = (
|
export const useFormattedDateTime = (
|
||||||
value: MaybeRef<string | Date | undefined>,
|
value: MaybeRef<string | Date | undefined>,
|
||||||
|
@ -10,3 +10,35 @@ export const useFormattedDateTime = (
|
||||||
return v ? formatter.format(new Date(v)) : ''
|
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