refs #3300 Handle shortcut keys in NewToot/Status
This commit is contained in:
parent
bd6e546765
commit
c204a53d96
|
@ -7,7 +7,7 @@
|
|||
:before-close="closeConfirm"
|
||||
width="600px"
|
||||
custom-class="new-toot-modal"
|
||||
ref="dialogRef"
|
||||
v-if="newTootModal"
|
||||
>
|
||||
<el-form v-on:submit.prevent="toot" role="form">
|
||||
<Quote :message="quoteToMessage" :displayNameStyle="displayNameStyle" v-if="quoteToMessage !== null" ref="quoteRef"></Quote>
|
||||
|
@ -145,7 +145,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, computed, onMounted, ComponentPublicInstance, nextTick } from 'vue'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { ElMessage, ElMessageBox, ElDialog } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Entity } from 'megalodon'
|
||||
import { useStore } from '@/store'
|
||||
import Visibility from '~/src/constants/visibility'
|
||||
|
@ -184,7 +184,6 @@ export default defineComponent({
|
|||
const imageRef = ref<HTMLInputElement>()
|
||||
const pollRef = ref<ComponentPublicInstance>()
|
||||
const spoilerRef = ref<HTMLElement>()
|
||||
const dialogRef = ref<InstanceType<typeof ElDialog>>()
|
||||
const quoteRef = ref<ComponentPublicInstance>()
|
||||
|
||||
const quoteToMessage = computed(() => store.state.TimelineSpace.Modals.NewToot.quoteToMessage)
|
||||
|
@ -230,6 +229,7 @@ export default defineComponent({
|
|||
store.dispatch(`${space}/${ACTION_TYPES.SETUP_LOADING}`)
|
||||
|
||||
onMounted(() => {
|
||||
console.log('new toot mounted')
|
||||
EventEmitter.on('image-uploaded', () => {
|
||||
if (previewRef.value) {
|
||||
statusHeight.value = statusHeight.value - previewRef.value.offsetHeight
|
||||
|
@ -366,6 +366,7 @@ export default defineComponent({
|
|||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_SENSITIVE}`, !sensitive.value)
|
||||
}
|
||||
const closeConfirm = (done: Function) => {
|
||||
if (!newTootModal.value) return
|
||||
if (statusText.value.length === 0) {
|
||||
done()
|
||||
} else {
|
||||
|
@ -467,7 +468,6 @@ export default defineComponent({
|
|||
imageRef,
|
||||
pollRef,
|
||||
spoilerRef,
|
||||
dialogRef,
|
||||
quoteRef,
|
||||
// computed
|
||||
quoteToMessage,
|
||||
|
|
|
@ -15,7 +15,14 @@
|
|||
autofocus
|
||||
>
|
||||
</textarea>
|
||||
<el-popover placement="bottom-start" width="300" trigger="manual" v-model:visible="openSuggest" popper-class="suggest-popper">
|
||||
<el-popover
|
||||
placement="bottom-start"
|
||||
width="300"
|
||||
trigger="focus"
|
||||
popper-class="suggest-popper"
|
||||
:popper-options="popperOptions()"
|
||||
ref="suggestRef"
|
||||
>
|
||||
<ul class="suggest-list">
|
||||
<li
|
||||
v-for="(item, index) in filteredSuggestion"
|
||||
|
@ -35,11 +42,11 @@
|
|||
</ul>
|
||||
<!-- dummy object to open suggest popper -->
|
||||
<template #reference>
|
||||
<span></span>
|
||||
<el-button type="text" ref="suggestButtonRef" class="dummy-button">dummy</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
<div>
|
||||
<el-popover placement="bottom" width="281" trigger="click" popper-class="new-toot-emoji-picker" ref="new_toot_emoji_picker">
|
||||
<el-popover placement="bottom" width="281" trigger="click" popper-class="new-toot-emoji-picker">
|
||||
<picker
|
||||
:data="emojiIndex"
|
||||
set="twitter"
|
||||
|
@ -63,11 +70,13 @@
|
|||
<script lang="ts">
|
||||
import 'emoji-mart-vue-fast/css/emoji-mart.css'
|
||||
import data from 'emoji-mart-vue-fast/data/all.json'
|
||||
import { defineComponent, computed, toRefs, ref } from 'vue'
|
||||
import { defineComponent, computed, toRefs, ref, onBeforeUnmount, onMounted } from 'vue'
|
||||
import { Picker, EmojiIndex } from 'emoji-mart-vue-fast/src'
|
||||
import { ElButton } from 'element-plus'
|
||||
|
||||
import suggestText from '@/utils/suggestText'
|
||||
import { useStore } from '@/store'
|
||||
import { MUTATION_TYPES, ACTION_TYPES } from '@/store/TimelineSpace/Modals/NewToot/Status'
|
||||
import { ACTION_TYPES } from '@/store/TimelineSpace/Modals/NewToot/Status'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'status',
|
||||
|
@ -99,14 +108,12 @@ export default defineComponent({
|
|||
const { modelValue } = toRefs(props)
|
||||
const highlightedIndex = ref(0)
|
||||
const statusRef = ref<HTMLTextAreaElement>()
|
||||
const suggestButtonRef = ref<InstanceType<typeof ElButton>>()
|
||||
const suggestRef = ref()
|
||||
|
||||
const filteredAccounts = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.filteredAccounts)
|
||||
const filteredHashtags = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.filteredHashtags)
|
||||
const filteredSuggestion = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.filteredSuggestion)
|
||||
const openSuggest = computed({
|
||||
get: () => store.state.TimelineSpace.Modals.NewToot.Status.openSuggest,
|
||||
set: (value: boolean) => store.commit(`${space}/${MUTATION_TYPES.CHANGE_OPEN_SUGGEST}`, value)
|
||||
})
|
||||
const startIndex = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.startIndex)
|
||||
const matchWord = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.matchWord)
|
||||
const customEmojis = computed(() => store.getters[`${space}/pickerEmojis`])
|
||||
|
@ -114,17 +121,47 @@ export default defineComponent({
|
|||
custom: customEmojis.value
|
||||
})
|
||||
|
||||
const onKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
suggestHighlight(highlightedIndex.value - 1)
|
||||
event.preventDefault()
|
||||
}
|
||||
if (event.key === 'ArrowDown') {
|
||||
suggestHighlight(highlightedIndex.value + 1)
|
||||
event.preventDefault()
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
selectCurrentItem()
|
||||
event.preventDefault()
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
closeSuggest()
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keyup', onKeyUp)
|
||||
closeSuggest()
|
||||
})
|
||||
onMounted(() => {
|
||||
document.addEventListener('keyup', onKeyUp)
|
||||
})
|
||||
|
||||
const openSuggest = () => {
|
||||
suggestButtonRef.value?.$el.focus()
|
||||
statusRef.value?.click()
|
||||
}
|
||||
const closeSuggest = () => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.CLOSE_SUGGEST}`)
|
||||
if (openSuggest.value) {
|
||||
highlightedIndex.value = 0
|
||||
}
|
||||
ctx.emit('suggestOpened', false)
|
||||
highlightedIndex.value = 0
|
||||
suggestButtonRef.value?.$el.blur()
|
||||
statusRef.value?.focus()
|
||||
}
|
||||
const suggestAccount = async (start: number, word: string) => {
|
||||
try {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.SUGGEST_ACCOUNT}`, { word: word, start: start })
|
||||
ctx.emit('suggestOpened', true)
|
||||
openSuggest()
|
||||
return true
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
|
@ -134,7 +171,7 @@ export default defineComponent({
|
|||
const suggestHashtag = async (start: number, word: string) => {
|
||||
try {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.SUGGEST_HASHTAG}`, { word: word, start: start })
|
||||
ctx.emit('suggestOpened', true)
|
||||
openSuggest()
|
||||
return true
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
|
@ -144,7 +181,7 @@ export default defineComponent({
|
|||
const suggestEmoji = async (start: number, word: string) => {
|
||||
try {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SUGGEST_EMOJI}`, { word: word, start: start })
|
||||
ctx.emit('suggestOpened', true)
|
||||
openSuggest()
|
||||
return true
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
|
@ -193,20 +230,25 @@ export default defineComponent({
|
|||
highlightedIndex.value = index
|
||||
}
|
||||
}
|
||||
const selectCurrentItem = () => {
|
||||
const item = filteredSuggestion.value[highlightedIndex.value]
|
||||
insertItem(item)
|
||||
}
|
||||
const insertItem = item => {
|
||||
console.log('inserted', item.name)
|
||||
if (item.code) {
|
||||
const str = `${modelValue.value.slice(0, startIndex.value - 1)}${item.code} ${modelValue.value.slice(
|
||||
startIndex.value + matchWord.value.length
|
||||
)}`
|
||||
ctx.emit('update:modelValue', str)
|
||||
} else {
|
||||
const str = `${modelValue.value.slice(0, startIndex.value - 1)}${item.name} ${modelValue.value.slice(
|
||||
startIndex.value + matchWord.value.length
|
||||
)}`
|
||||
console.log(str)
|
||||
ctx.emit('update:modelValue', str)
|
||||
if (item) {
|
||||
if (item.code) {
|
||||
const str = `${modelValue.value.slice(0, startIndex.value - 1)}${item.code} ${modelValue.value.slice(
|
||||
startIndex.value + matchWord.value.length
|
||||
)}`
|
||||
ctx.emit('update:modelValue', str)
|
||||
} else {
|
||||
const str = `${modelValue.value.slice(0, startIndex.value - 1)}${item.name} ${modelValue.value.slice(
|
||||
startIndex.value + matchWord.value.length
|
||||
)}`
|
||||
ctx.emit('update:modelValue', str)
|
||||
}
|
||||
}
|
||||
|
||||
closeSuggest()
|
||||
}
|
||||
const selectEmoji = emoji => {
|
||||
|
@ -217,19 +259,38 @@ export default defineComponent({
|
|||
// Custom emoji don't have natvie code
|
||||
ctx.emit('update:modelValue', `${modelValue.value.slice(0, current)}${emoji.name} ${modelValue.value.slice(current)}`)
|
||||
}
|
||||
closeSuggest()
|
||||
}
|
||||
const popperOptions = () => {
|
||||
const element = document.querySelector('#status_textarea')
|
||||
return {
|
||||
modifiers: [
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary: element,
|
||||
rootBoundary: 'viewport',
|
||||
altBoundary: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
statusRef,
|
||||
suggestButtonRef,
|
||||
suggestRef,
|
||||
emojiIndex,
|
||||
highlightedIndex,
|
||||
filteredAccounts,
|
||||
filteredHashtags,
|
||||
filteredSuggestion,
|
||||
openSuggest,
|
||||
startSuggest,
|
||||
suggestHighlight,
|
||||
insertItem,
|
||||
selectEmoji
|
||||
selectEmoji,
|
||||
popperOptions
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -307,6 +368,15 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
.dummy-button {
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.emoji-selector {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
|
|
|
@ -39,7 +39,6 @@ export type StatusState = {
|
|||
filteredAccounts: Array<SuggestAccount>
|
||||
filteredHashtags: Array<SuggestHashtag>
|
||||
filteredEmojis: Array<SuggestEmoji>
|
||||
openSuggest: boolean
|
||||
startIndex: number
|
||||
matchWord: string
|
||||
client: MegalodonInterface | null
|
||||
|
@ -50,7 +49,6 @@ const state = (): StatusState => ({
|
|||
filteredAccounts: [],
|
||||
filteredHashtags: [],
|
||||
filteredEmojis: [],
|
||||
openSuggest: false,
|
||||
startIndex: 0,
|
||||
matchWord: '',
|
||||
client: null
|
||||
|
@ -63,7 +61,6 @@ export const MUTATION_TYPES = {
|
|||
CLEAR_FILTERED_HASHTAGS: 'clearFilteredHashtags',
|
||||
UPDATE_FILTERED_EMOJIS: 'updateFilteredEmojis',
|
||||
CLEAR_FILTERED_EMOJIS: 'clearFilteredEmojis',
|
||||
CHANGE_OPEN_SUGGEST: 'changeOpenSuggest',
|
||||
CHANGE_START_INDEX: 'changeStartIndex',
|
||||
CHANGE_MATCH_WORD: 'changeMatchWord',
|
||||
FILTERED_SUGGESTION_FROM_HASHTAGS: 'filteredSuggestionFromHashtags',
|
||||
|
@ -123,9 +120,6 @@ const mutations: MutationTree<StatusState> = {
|
|||
[MUTATION_TYPES.UPDATE_FILTERED_EMOJIS]: (state, emojis: Array<SuggestEmoji>) => {
|
||||
state.filteredEmojis = emojis
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_OPEN_SUGGEST]: (state, value: boolean) => {
|
||||
state.openSuggest = value
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_START_INDEX]: (state, index: number) => {
|
||||
state.startIndex = index
|
||||
},
|
||||
|
@ -177,7 +171,6 @@ const actions: ActionTree<StatusState, RootState> = {
|
|||
const matched = accounts.map(account => account.acct).filter(acct => acct.includes(target))
|
||||
if (matched.length === 0) throw new Error('Empty')
|
||||
commit(MUTATION_TYPES.APPEND_FILTERED_ACCOUNTS, matched)
|
||||
commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
|
||||
commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
|
||||
commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
|
||||
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS)
|
||||
|
@ -201,7 +194,6 @@ const actions: ActionTree<StatusState, RootState> = {
|
|||
ownerID: rootState.TimelineSpace.account._id!,
|
||||
accts: res.data.map(a => a.acct)
|
||||
} as InsertAccountCache)
|
||||
commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
|
||||
commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
|
||||
commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
|
||||
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS)
|
||||
|
@ -220,7 +212,6 @@ const actions: ActionTree<StatusState, RootState> = {
|
|||
const matched = tags.map(tag => tag.tagName).filter(tag => tag.includes(target))
|
||||
if (matched.length === 0) throw new Error('Empty')
|
||||
commit(MUTATION_TYPES.APPEND_FILTERED_HASHTAGS, matched)
|
||||
commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
|
||||
commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
|
||||
commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
|
||||
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS)
|
||||
|
@ -244,7 +235,6 @@ const actions: ActionTree<StatusState, RootState> = {
|
|||
'insert-cache-hashtags',
|
||||
res.data.hashtags.map(tag => tag.name)
|
||||
)
|
||||
commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
|
||||
commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
|
||||
commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
|
||||
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS)
|
||||
|
@ -292,7 +282,6 @@ const actions: ActionTree<StatusState, RootState> = {
|
|||
return array.findIndex(ar => e.name === ar.name) === i
|
||||
})
|
||||
)
|
||||
commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
|
||||
commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
|
||||
commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
|
||||
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_EMOJIS)
|
||||
|
@ -305,7 +294,6 @@ const actions: ActionTree<StatusState, RootState> = {
|
|||
},
|
||||
[ACTION_TYPES.CLOSE_SUGGEST]: ({ commit, dispatch }) => {
|
||||
dispatch('cancelRequest')
|
||||
commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, false)
|
||||
commit(MUTATION_TYPES.CHANGE_START_INDEX, 0)
|
||||
commit(MUTATION_TYPES.CHANGE_MATCH_WORD, '')
|
||||
commit(MUTATION_TYPES.CLEAR_FILTERED_SUGGESTION)
|
||||
|
|
Loading…
Reference in New Issue