Merge pull request #4016 from h3poteto/iss-3771
refs #3771 Add compose window to footer
This commit is contained in:
commit
29100da80a
|
@ -294,7 +294,7 @@
|
|||
"add_image": "Add images",
|
||||
"poll": "Add a poll",
|
||||
"change_visibility": "Change visibility",
|
||||
"change_sensitive": "Change sensitive",
|
||||
"change_sensitive": "Mark media as sensitive",
|
||||
"add_cw": "Add content warning",
|
||||
"pined_hashtag": "Pin the hashtag"
|
||||
},
|
||||
|
|
|
@ -11,7 +11,13 @@
|
|||
<header class="header" style="-webkit-app-region: drag">
|
||||
<header-menu></header-menu>
|
||||
</header>
|
||||
<contents></contents>
|
||||
<div class="contents-wrapper" ref="contentsRef">
|
||||
<contents />
|
||||
</div>
|
||||
<div class="compose-wrapper">
|
||||
<compose />
|
||||
<resize-observer @notify="composeResized" />
|
||||
</div>
|
||||
</div>
|
||||
<modals></modals>
|
||||
<receive-drop v-show="droppableVisible"></receive-drop>
|
||||
|
@ -26,6 +32,7 @@ import { useI18next } from 'vue3-i18next'
|
|||
import SideMenu from './TimelineSpace/SideMenu.vue'
|
||||
import HeaderMenu from './TimelineSpace/HeaderMenu.vue'
|
||||
import Contents from './TimelineSpace/Contents.vue'
|
||||
import Compose from './TimelineSpace/Compose.vue'
|
||||
import Modals from './TimelineSpace/Modals.vue'
|
||||
import Mousetrap from 'mousetrap'
|
||||
import ReceiveDrop from './TimelineSpace/ReceiveDrop.vue'
|
||||
|
@ -42,7 +49,7 @@ import { ACTION_TYPES as NEW_TOOT_ACTION } from '@/store/TimelineSpace/Modals/Ne
|
|||
|
||||
export default defineComponent({
|
||||
name: 'timeline-space',
|
||||
components: { SideMenu, HeaderMenu, Modals, Contents, ReceiveDrop },
|
||||
components: { SideMenu, HeaderMenu, Modals, Contents, ReceiveDrop, Compose },
|
||||
setup() {
|
||||
const space = 'TimelineSpace'
|
||||
const store = useStore()
|
||||
|
@ -51,6 +58,7 @@ export default defineComponent({
|
|||
|
||||
const dropTarget = ref<any>(null)
|
||||
const droppableVisible = ref<boolean>(false)
|
||||
const contentsRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const loading = computed(() => store.state.TimelineSpace.loading)
|
||||
const collapse = computed(() => store.state.TimelineSpace.SideMenu.collapse)
|
||||
|
@ -155,10 +163,18 @@ export default defineComponent({
|
|||
e.preventDefault()
|
||||
}
|
||||
|
||||
const composeResized = (event: { width: number; height: number }) => {
|
||||
if (contentsRef.value) {
|
||||
contentsRef.value.style.setProperty('height', `calc(100% - ${event.height}px)`)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
collapse,
|
||||
droppableVisible
|
||||
droppableVisible,
|
||||
composeResized,
|
||||
contentsRef
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -169,6 +185,12 @@ export default defineComponent({
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.compose-wrapper {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 0 12px 18px 12px;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin-left: 180px;
|
||||
height: 100%;
|
||||
|
|
|
@ -0,0 +1,512 @@
|
|||
<template>
|
||||
<div class="compose">
|
||||
<el-form :model="form" class="compose-form">
|
||||
<el-input v-model="form.spoiler" class="spoiler" :placeholder="$t('modals.new_toot.cw')" v-if="cw" />
|
||||
<el-input
|
||||
v-model="form.status"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2 }"
|
||||
:placeholder="$t('modals.new_toot.status')"
|
||||
ref="statusRef"
|
||||
/>
|
||||
<div class="preview" ref="previewRef">
|
||||
<div class="image-wrapper" v-for="media in attachments" :key="media.id">
|
||||
<img :src="media.preview_url" class="preview-image" />
|
||||
<el-button class="remove-image" link @click="removeAttachment(media)"><font-awesome-icon icon="circle-xmark" /></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nsfw" v-if="attachments.length > 0">
|
||||
<el-checkbox v-model="nsfw">{{ $t('modals.new_toot.footer.change_sensitive') }}</el-checkbox>
|
||||
</div>
|
||||
<div class="poll" v-if="poll.options.length > 0">
|
||||
<ul class="options-list">
|
||||
<li class="option" v-for="(option, index) in poll.options" :key="index">
|
||||
<el-radio :disabled="true" :label="index">
|
||||
<el-input :placeholder="`Choice ${index}`" v-model="poll.options[index]" size="small"></el-input>
|
||||
<el-button class="remove-poll" link size="small" @click="removePollOption(index)"
|
||||
><font-awesome-icon icon="xmark"
|
||||
/></el-button>
|
||||
</el-radio>
|
||||
</li>
|
||||
</ul>
|
||||
<el-button class="add-poll" type="info" size="small" @click="addPollOption">{{ $t('modals.new_toot.poll.add_choice') }}</el-button>
|
||||
<el-select v-model="poll.expires_in" size="small" value-key="value">
|
||||
<el-option v-for="exp in expiresList" :key="exp.value" :label="exp.label" :value="exp.value"> </el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<el-button-group class="tool-buttons">
|
||||
<el-button link size="default" @click="selectImage">
|
||||
<font-awesome-icon icon="camera" />
|
||||
</el-button>
|
||||
<input name="image" type="file" class="image-input" ref="imageRef" @change="onChangeImage" />
|
||||
<el-button link size="default" @click="togglePoll">
|
||||
<font-awesome-icon icon="square-poll-horizontal" />
|
||||
</el-button>
|
||||
<div>
|
||||
<el-dropdown trigger="click" @command="changeVisibility">
|
||||
<el-button size="default" link :title="$t('modals.new_toot.footer.change_visibility')">
|
||||
<font-awesome-icon :icon="visibilityIcon" />
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :command="visibilityList.Public.key">
|
||||
<font-awesome-icon icon="globe" class="privacy-icon" />
|
||||
{{ $t(visibilityList.Public.name) }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :command="visibilityList.Unlisted.key">
|
||||
<font-awesome-icon icon="unlock" class="privacy-icon" />
|
||||
{{ $t(visibilityList.Unlisted.name) }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :command="visibilityList.Private.key">
|
||||
<font-awesome-icon icon="lock" class="privacy-icon" />
|
||||
{{ $t(visibilityList.Private.name) }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :command="visibilityList.Direct.key">
|
||||
<font-awesome-icon icon="envelope" class="privacy-icon" size="sm" />
|
||||
{{ $t(visibilityList.Direct.name) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-popover placement="top" width="0" :visible="emojiVisible" popper-class="new-toot-emoji-picker">
|
||||
<picker
|
||||
v-if="emojiData !== null"
|
||||
:data="emojiData"
|
||||
set="twitter"
|
||||
:autoFocus="true"
|
||||
@select="selectEmoji"
|
||||
:perLine="7"
|
||||
:emojiSize="24"
|
||||
:showPreview="false"
|
||||
:emojiTooltip="true"
|
||||
/>
|
||||
<template #reference>
|
||||
<el-button link size="default" @click="emojiVisible = !emojiVisible">
|
||||
<font-awesome-icon :icon="['far', 'face-smile']" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-button link size="default" @click="cw = !cw"> CW </el-button>
|
||||
</el-button-group>
|
||||
<div class="actions-group">
|
||||
<span>500</span>
|
||||
<el-button type="primary" @click="post" :loading="loading"> {{ $t('modals.new_toot.toot') }} </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, computed, ref, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import generator, { Entity, MegalodonInterface } from 'megalodon'
|
||||
import emojiDefault from 'emoji-mart-vue-fast/data/all.json'
|
||||
import { Picker, EmojiIndex } from 'emoji-mart-vue-fast/src'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { useStore } from '@/store'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import visibilityList from '~/src/constants/visibility'
|
||||
import { MUTATION_TYPES } from '@/store/TimelineSpace/Compose'
|
||||
|
||||
type Expire = {
|
||||
label: string
|
||||
value: number
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Compose',
|
||||
components: { Picker },
|
||||
setup() {
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
const i18n = useI18next()
|
||||
const space = 'TimelineSpace/Compose'
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
const userAgent = computed(() => store.state.App.userAgent)
|
||||
const visibilityIcon = computed(() => {
|
||||
switch (visibility.value) {
|
||||
case visibilityList.Public.key:
|
||||
return 'globe'
|
||||
case visibilityList.Unlisted.key:
|
||||
return 'unlock'
|
||||
case visibilityList.Private.key:
|
||||
return 'lock'
|
||||
case visibilityList.Direct.key:
|
||||
return 'envelope'
|
||||
default:
|
||||
return 'globe'
|
||||
}
|
||||
})
|
||||
const expiresList = reactive<Array<Expire>>([
|
||||
{
|
||||
label: i18n.t('modals.new_toot.poll.expires.5_minutes'),
|
||||
value: 60 * 5
|
||||
},
|
||||
{
|
||||
label: i18n.t('modals.new_toot.poll.expires.30_minutes'),
|
||||
value: 60 * 30
|
||||
},
|
||||
{
|
||||
label: i18n.t('modals.new_toot.poll.expires.1_hour'),
|
||||
value: 3600
|
||||
},
|
||||
{
|
||||
label: i18n.t('modals.new_toot.poll.expires.6_hours'),
|
||||
value: 3600 * 6
|
||||
},
|
||||
{
|
||||
label: i18n.t('modals.new_toot.poll.expires.1_day'),
|
||||
value: 3600 * 24
|
||||
},
|
||||
{
|
||||
label: i18n.t('modals.new_toot.poll.expires.3_days'),
|
||||
value: 3600 * 24 * 3
|
||||
},
|
||||
{
|
||||
label: i18n.t('modals.new_toot.poll.expires.7_days'),
|
||||
value: 3600 * 24 * 7
|
||||
}
|
||||
])
|
||||
|
||||
const emojiData = ref<EmojiIndex | null>(null)
|
||||
const client = ref<MegalodonInterface | null>(null)
|
||||
const form = reactive({
|
||||
status: '',
|
||||
spoiler: ''
|
||||
})
|
||||
const attachments = ref<Array<Entity.Attachment | Entity.AsyncAttachment>>([])
|
||||
const cw = ref<boolean>(false)
|
||||
const visibility = ref(visibilityList.Public.key)
|
||||
const nsfw = ref<boolean>(false)
|
||||
const inReplyTo = computed(() => store.state.TimelineSpace.Compose.inReplyTo)
|
||||
const poll = reactive<{ options: Array<string>; expires_in: number }>({
|
||||
options: [],
|
||||
expires_in: 86400
|
||||
})
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
const emojiVisible = ref<boolean>(false)
|
||||
const imageRef = ref<any>(null)
|
||||
const statusRef = ref<any>(null)
|
||||
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
const c = generator(s.sns, s.baseURL, a.accessToken, userAgent.value)
|
||||
client.value = c
|
||||
|
||||
const res = await c.getInstanceCustomEmojis()
|
||||
const customEmojis = res.data
|
||||
.map(emoji => {
|
||||
return {
|
||||
name: `:${emoji.shortcode}:`,
|
||||
image: emoji.url
|
||||
}
|
||||
})
|
||||
.filter((e, i, array) => {
|
||||
return array.findIndex(ar => e.name === ar.name) === i
|
||||
})
|
||||
.map(e => ({
|
||||
name: e.name,
|
||||
short_names: [e.name],
|
||||
text: e.name,
|
||||
emoticons: [],
|
||||
keywords: [e.name],
|
||||
imageUrl: e.image
|
||||
}))
|
||||
emojiData.value = new EmojiIndex(emojiDefault, { custom: customEmojis })
|
||||
})
|
||||
|
||||
watch(inReplyTo, current => {
|
||||
if (current) {
|
||||
form.status = `@${current.acct} `
|
||||
}
|
||||
})
|
||||
|
||||
const post = async () => {
|
||||
if (!client.value) {
|
||||
return
|
||||
}
|
||||
let options = {
|
||||
visibility: visibility.value
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
if (attachments.value.length > 0) {
|
||||
options = Object.assign(options, {
|
||||
media_ids: attachments.value.map(m => m.id)
|
||||
})
|
||||
if (nsfw.value) {
|
||||
options = Object.assign(options, {
|
||||
sensitive: nsfw.value
|
||||
})
|
||||
}
|
||||
}
|
||||
if (form.spoiler.length > 0) {
|
||||
options = Object.assign(options, {
|
||||
spoiler_text: form.spoiler
|
||||
})
|
||||
}
|
||||
if (inReplyTo.value) {
|
||||
options = Object.assign(options, {
|
||||
in_reply_to_id: inReplyTo.value?.id
|
||||
})
|
||||
}
|
||||
|
||||
if (poll.options.length > 0) {
|
||||
options = Object.assign(options, {
|
||||
poll: poll
|
||||
})
|
||||
}
|
||||
|
||||
await client.value.postStatus(form.status, options)
|
||||
clear()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
form.status = ''
|
||||
form.spoiler = ''
|
||||
attachments.value = []
|
||||
cw.value = false
|
||||
emojiVisible.value = false
|
||||
store.commit(`${space}/${MUTATION_TYPES.CLEAR_REPLY_TO_ID}`)
|
||||
poll.options = []
|
||||
}
|
||||
|
||||
const selectImage = () => {
|
||||
imageRef?.value?.click()
|
||||
}
|
||||
|
||||
const onChangeImage = async (e: Event) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
const file = target.files?.item(0)
|
||||
if (file === null || file === undefined) {
|
||||
return
|
||||
}
|
||||
await uploadImage(file)
|
||||
}
|
||||
|
||||
const uploadImage = async (file: File) => {
|
||||
if (!client.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await client.value.uploadMedia(file)
|
||||
attachments.value = [...attachments.value, res.data]
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const removeAttachment = async (attachment: Entity.Attachment | Entity.AsyncAttachment) => {
|
||||
attachments.value = attachments.value.filter(e => e.id !== attachment.id)
|
||||
}
|
||||
|
||||
const selectEmoji = emoji => {
|
||||
if (!statusRef.value) {
|
||||
return
|
||||
}
|
||||
const current = statusRef.value?.textarea.selectionStart
|
||||
if (emoji.native) {
|
||||
form.status = form.status.slice(0, current) + emoji.native + form.status.slice(current)
|
||||
} else {
|
||||
form.status = form.status.slice(0, current) + emoji.name + form.status.slice(current)
|
||||
}
|
||||
emojiVisible.value = false
|
||||
}
|
||||
|
||||
const changeVisibility = (value: 'public' | 'unlisted' | 'private' | 'direct') => {
|
||||
visibility.value = value
|
||||
}
|
||||
|
||||
const togglePoll = () => {
|
||||
if (poll.options.length > 0) {
|
||||
poll.options = []
|
||||
} else {
|
||||
poll.options = ['', '']
|
||||
}
|
||||
}
|
||||
|
||||
const addPollOption = () => {
|
||||
poll.options = [...poll.options, '']
|
||||
}
|
||||
|
||||
const removePollOption = (index: number) => {
|
||||
poll.options = poll.options.filter((_, i) => i !== index)
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
post,
|
||||
imageRef,
|
||||
selectImage,
|
||||
onChangeImage,
|
||||
loading,
|
||||
attachments,
|
||||
removeAttachment,
|
||||
emojiData,
|
||||
selectEmoji,
|
||||
statusRef,
|
||||
emojiVisible,
|
||||
cw,
|
||||
visibilityList,
|
||||
visibilityIcon,
|
||||
changeVisibility,
|
||||
nsfw,
|
||||
poll,
|
||||
togglePoll,
|
||||
expiresList,
|
||||
addPollOption,
|
||||
removePollOption
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.compose {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.compose-form {
|
||||
height: calc(100% - 24px);
|
||||
|
||||
.spoiler {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form :deep(.el-textarea__inner) {
|
||||
color: var(--theme-primary-color);
|
||||
background-color: var(--theme-background-color);
|
||||
box-shadow: 0 0 0 1px var(--theme-border-color, var(--theme-border-color)) inset;
|
||||
}
|
||||
|
||||
.compose-form :deep(.el-input__wrapper) {
|
||||
background-color: var(--theme-background-color);
|
||||
box-shadow: 0 0 0 1px var(--theme-border-color, var(--theme-border-color)) inset;
|
||||
}
|
||||
|
||||
.compose-form :deep(.el-input__inner) {
|
||||
color: var(--theme-primary-color);
|
||||
}
|
||||
|
||||
.preview {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 10%;
|
||||
height: 80px;
|
||||
margin: 4px;
|
||||
|
||||
.preview-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.remove-image {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
|
||||
.fa-icon {
|
||||
font-size: 0.9rem;
|
||||
width: auto;
|
||||
height: 1em;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.poll {
|
||||
.options-list {
|
||||
list-style: none;
|
||||
padding-left: 16px;
|
||||
|
||||
.poll-option {
|
||||
line-height: 38px;
|
||||
|
||||
.remove-poll {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-poll {
|
||||
margin: 0 4px 0 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.poll :deep(.el-input__inner) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 4px;
|
||||
|
||||
.tool-buttons {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.image-input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-group {
|
||||
span {
|
||||
margin-right: 8px;
|
||||
color: var(--theme-secondary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.privacy-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.new-toot-emoji-picker {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
|
||||
.el-popper__arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -27,12 +27,6 @@
|
|||
</template>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
|
||||
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
|
||||
<el-button type="primary" @click="upper" circle>
|
||||
<font-awesome-icon icon="angle-up" class="upper-icon" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -204,10 +198,6 @@ export default defineComponent({
|
|||
}, 500)
|
||||
})
|
||||
}
|
||||
const upper = () => {
|
||||
scroller.value.scrollToItem(0)
|
||||
focusedId.value = null
|
||||
}
|
||||
const focusNext = () => {
|
||||
if (currentFocusedIndex.value === -1) {
|
||||
focusedId.value = timeline.value[0].uri + timeline.value[0].id
|
||||
|
@ -245,7 +235,6 @@ export default defineComponent({
|
|||
focusToot,
|
||||
openSideBar,
|
||||
heading,
|
||||
upper,
|
||||
account
|
||||
}
|
||||
}
|
||||
|
@ -269,39 +258,6 @@ export default defineComponent({
|
|||
.loading-card:empty {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.upper {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.upper-with-side-bar {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: calc(20px + var(--current-sidebar-width));
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.upper-icon {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.unread {
|
||||
position: fixed;
|
||||
right: 24px;
|
||||
top: 52px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
color: #ffffff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 0 0 2px 2px;
|
||||
z-index: 1;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
@ -255,9 +255,9 @@ import { usernameWithStyle, accountNameWithStyle } from '@/utils/username'
|
|||
import { parseDatetime } from '@/utils/datetime'
|
||||
import { MUTATION_TYPES as SIDEBAR_MUTATION, ACTION_TYPES as SIDEBAR_ACTION } from '@/store/TimelineSpace/Contents/SideBar'
|
||||
import { ACTION_TYPES as PROFILE_ACTION } from '@/store/TimelineSpace/Contents/SideBar/AccountProfile'
|
||||
import { ACTION_TYPES as NEW_ACTION } from '@/store/TimelineSpace/Modals/NewToot'
|
||||
import { ACTION_TYPES as DETAIL_ACTION } from '@/store/TimelineSpace/Contents/SideBar/TootDetail'
|
||||
import { ACTION_TYPES as REPORT_ACTION } from '@/store/TimelineSpace/Modals/Report'
|
||||
import { MUTATION_TYPES as COMPOSE_MUTATION } from '@/store/TimelineSpace/Compose'
|
||||
import { ACTION_TYPES as MUTE_ACTION } from '@/store/TimelineSpace/Modals/MuteConfirm'
|
||||
import { ACTION_TYPES as VIEWER_ACTION } from '@/store/TimelineSpace/Modals/ImageViewer'
|
||||
import { ACTION_TYPES } from '@/store/organisms/Toot'
|
||||
|
@ -477,7 +477,10 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
const openReply = () => {
|
||||
store.dispatch(`TimelineSpace/Modals/NewToot/${NEW_ACTION.OPEN_REPLY}`, originalMessage.value)
|
||||
store.commit(`TimelineSpace/Compose/${COMPOSE_MUTATION.SET_REPLY_TO_ID}`, {
|
||||
acct: originalMessage.value.account.acct,
|
||||
id: originalMessage.value.id
|
||||
})
|
||||
}
|
||||
const openDetail = (message: Entity.Status) => {
|
||||
store.dispatch(`TimelineSpace/Contents/SideBar/${SIDEBAR_ACTION.OPEN_TOOT_COMPONENT}`)
|
||||
|
|
|
@ -11,6 +11,7 @@ import { MyWindow } from '~/src/types/global'
|
|||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { Setting } from '~/src/types/setting'
|
||||
import { DefaultSetting } from '~/src/constants/initializer/setting'
|
||||
import Compose, { ComposeState } from './TimelineSpace/Compose'
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
|
@ -201,6 +202,7 @@ type TimelineSpaceModule = {
|
|||
HeaderMenu: HeaderMenuState
|
||||
Modals: ModalsModuleState
|
||||
Contents: ContentsModuleState
|
||||
Compose: ComposeState
|
||||
}
|
||||
|
||||
export type TimelineSpaceModuleState = TimelineSpaceModule & TimelineSpaceState
|
||||
|
@ -211,7 +213,8 @@ const TimelineSpace: Module<TimelineSpaceState, RootState> = {
|
|||
SideMenu,
|
||||
HeaderMenu,
|
||||
Modals,
|
||||
Contents
|
||||
Contents,
|
||||
Compose
|
||||
},
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { Module, MutationTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export type ReplyTo = {
|
||||
acct: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export type ComposeState = {
|
||||
inReplyTo: ReplyTo | null
|
||||
}
|
||||
|
||||
const state = (): ComposeState => ({
|
||||
inReplyTo: null
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
SET_REPLY_TO_ID: 'setReplyToId',
|
||||
CLEAR_REPLY_TO_ID: 'clearReplyToId'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<ComposeState> = {
|
||||
[MUTATION_TYPES.SET_REPLY_TO_ID]: (state, inReplyTo: ReplyTo) => {
|
||||
state.inReplyTo = inReplyTo
|
||||
},
|
||||
[MUTATION_TYPES.CLEAR_REPLY_TO_ID]: state => {
|
||||
state.inReplyTo = null
|
||||
}
|
||||
}
|
||||
|
||||
const Compose: Module<ComposeState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations
|
||||
}
|
||||
|
||||
export default Compose
|
Loading…
Reference in New Issue