Rewrite Modals/NewToot/Status with composition API
This commit is contained in:
parent
71a8e4c488
commit
29f3776c08
|
@ -17,14 +17,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Status
|
<Status
|
||||||
v-model="status"
|
:modelValue="status"
|
||||||
|
@update:modelValue="status = $event"
|
||||||
:opened="newTootModal"
|
:opened="newTootModal"
|
||||||
:fixCursorPos="hashtagInserting"
|
:fixCursorPos="hashtagInserting"
|
||||||
:height="statusHeight"
|
:height="statusHeight"
|
||||||
@paste="onPaste"
|
@paste="onPaste"
|
||||||
@toot="toot"
|
@toot="toot"
|
||||||
@pickerOpened="innerElementOpened"
|
|
||||||
@suggestOpened="innerElementOpened"
|
|
||||||
/>
|
/>
|
||||||
</el-form>
|
</el-form>
|
||||||
<Poll v-if="openPoll" v-model:polls="polls" v-model:expire="pollExpire" @addPoll="addPoll" @removePoll="removePoll" ref="poll"></Poll>
|
<Poll v-if="openPoll" v-model:polls="polls" v-model:expire="pollExpire" @addPoll="addPoll" @removePoll="removePoll" ref="poll"></Poll>
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="status"
|
:value="modelValue"
|
||||||
ref="status"
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
@paste="onPaste"
|
ref="statusRef"
|
||||||
|
@paste="$emit('paste', $event)"
|
||||||
v-on:input="startSuggest"
|
v-on:input="startSuggest"
|
||||||
:placeholder="$t('modals.new_toot.status')"
|
:placeholder="$t('modals.new_toot.status')"
|
||||||
role="textbox"
|
role="textbox"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
aria-multiline="true"
|
aria-multiline="true"
|
||||||
:style="`height: ${height}px`"
|
:style="`height: ${height}px`"
|
||||||
|
v-focus
|
||||||
autofocus
|
autofocus
|
||||||
>
|
>
|
||||||
</textarea>
|
</textarea>
|
||||||
|
@ -19,7 +21,7 @@
|
||||||
v-for="(item, index) in filteredSuggestion"
|
v-for="(item, index) in filteredSuggestion"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="insertItem(item)"
|
@click="insertItem(item)"
|
||||||
@mouseover="highlightedIndex = index"
|
@mouseover="suggestHighlight(index)"
|
||||||
:class="{ highlighted: highlightedIndex === index }"
|
:class="{ highlighted: highlightedIndex === index }"
|
||||||
>
|
>
|
||||||
<span v-if="item.image">
|
<span v-if="item.image">
|
||||||
|
@ -59,23 +61,24 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import 'emoji-mart-vue-fast/css/emoji-mart.css'
|
import 'emoji-mart-vue-fast/css/emoji-mart.css'
|
||||||
import data from 'emoji-mart-vue-fast/data/all.json'
|
import data from 'emoji-mart-vue-fast/data/all.json'
|
||||||
import { mapState, mapGetters } from 'vuex'
|
import { defineComponent, computed, toRefs, ref } from 'vue'
|
||||||
import { Picker, EmojiIndex } from 'emoji-mart-vue-fast/src'
|
import { Picker, EmojiIndex } from 'emoji-mart-vue-fast/src'
|
||||||
import suggestText from '@/utils/suggestText'
|
import suggestText from '@/utils/suggestText'
|
||||||
|
import { useStore } from '@/store'
|
||||||
|
import { MUTATION_TYPES, ACTION_TYPES } from '@/store/TimelineSpace/Modals/NewToot/Status'
|
||||||
|
|
||||||
const emojiIndex = new EmojiIndex(data)
|
export default defineComponent({
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'status',
|
name: 'status',
|
||||||
components: {
|
components: {
|
||||||
Picker
|
Picker
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
modelValue: {
|
||||||
type: String
|
type: String,
|
||||||
|
default: ''
|
||||||
},
|
},
|
||||||
opened: {
|
opened: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -90,194 +93,145 @@ export default {
|
||||||
default: 120
|
default: 120
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
setup(props, ctx) {
|
||||||
return {
|
const space = 'TimelineSpace/Modals/NewToot/Status'
|
||||||
highlightedIndex: 0,
|
const store = useStore()
|
||||||
openEmojiPicker: false,
|
const emojiIndex = new EmojiIndex(data)
|
||||||
emojiIndex: emojiIndex
|
const { modelValue } = toRefs(props)
|
||||||
}
|
const highlightedIndex = ref(0)
|
||||||
},
|
const statusRef = ref<HTMLTextAreaElement>()
|
||||||
computed: {
|
|
||||||
...mapState('TimelineSpace/Modals/NewToot/Status', {
|
const filteredAccounts = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.filteredAccounts)
|
||||||
filteredAccounts: state => state.filteredAccounts,
|
const filteredHashtags = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.filteredHashtags)
|
||||||
filteredHashtags: state => state.filteredHashtags,
|
const filteredSuggestion = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.filteredSuggestion)
|
||||||
openSuggest: state => state.openSuggest,
|
const openSuggest = computed({
|
||||||
startIndex: state => state.startIndex,
|
get: () => store.state.TimelineSpace.Modals.NewToot.Status.openSuggest,
|
||||||
matchWord: state => state.matchWord,
|
set: (value: boolean) => store.commit(`${space}/${MUTATION_TYPES.CHANGE_OPEN_SUGGEST}`, value)
|
||||||
filteredSuggestion: state => state.filteredSuggestion
|
|
||||||
}),
|
|
||||||
...mapGetters('TimelineSpace/Modals/NewToot/Status', ['pickerEmojis']),
|
|
||||||
status: {
|
|
||||||
get: function () {
|
|
||||||
return this.value
|
|
||||||
},
|
|
||||||
set: function (value) {
|
|
||||||
this.$emit('input', value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// When change account, the new toot modal is recreated.
|
|
||||||
// So can not catch open event in watch.
|
|
||||||
this.$refs.status.focus()
|
|
||||||
if (this.fixCursorPos) {
|
|
||||||
this.$refs.status.setSelectionRange(0, 0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
opened: function (newState, oldState) {
|
|
||||||
if (!oldState && newState) {
|
|
||||||
this.$nextTick(function () {
|
|
||||||
this.$refs.status.focus()
|
|
||||||
if (this.fixCursorPos) {
|
|
||||||
this.$refs.status.setSelectionRange(0, 0)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
} else if (oldState && !newState) {
|
const startIndex = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.startIndex)
|
||||||
this.closeSuggest()
|
const matchWord = computed(() => store.state.TimelineSpace.Modals.NewToot.Status.matchWord)
|
||||||
|
const pickerEmojis = computed(() => store.getters[`${space}/pickerEmojis`])
|
||||||
|
|
||||||
|
const closeSuggest = () => {
|
||||||
|
store.dispatch(`${space}/${ACTION_TYPES.CLOSE_SUGGEST}`)
|
||||||
|
if (openSuggest.value) {
|
||||||
|
highlightedIndex.value = 0
|
||||||
|
}
|
||||||
|
ctx.emit('suggestOpened', false)
|
||||||
|
}
|
||||||
|
const suggestAccount = async (start: number, word: string) => {
|
||||||
|
try {
|
||||||
|
await store.dispatch(`${space}/${ACTION_TYPES.SUGGEST_ACCOUNT}`, { word: word, start: start })
|
||||||
|
ctx.emit('suggestOpened', true)
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
const suggestHashtag = async (start: number, word: string) => {
|
||||||
methods: {
|
try {
|
||||||
async startSuggest(e) {
|
await store.dispatch(`${space}/${ACTION_TYPES.SUGGEST_HASHTAG}`, { word: word, start: start })
|
||||||
const currentValue = e.target.value
|
ctx.emit('suggestOpened', true)
|
||||||
// Start suggest after user stop writing
|
return true
|
||||||
setTimeout(async () => {
|
} catch (err) {
|
||||||
if (currentValue === this.status) {
|
console.log(err)
|
||||||
await this.suggest(e)
|
return false
|
||||||
}
|
}
|
||||||
}, 700)
|
}
|
||||||
},
|
const suggestEmoji = async (start: number, word: string) => {
|
||||||
async suggest(e) {
|
try {
|
||||||
|
store.dispatch(`${space}/${ACTION_TYPES.SUGGEST_EMOJI}`, { word: word, start: start })
|
||||||
|
ctx.emit('suggestOpened', true)
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const suggest = async (e: Event) => {
|
||||||
|
const target = e.target as HTMLInputElement
|
||||||
// e.target.sectionStart: Cursor position
|
// e.target.sectionStart: Cursor position
|
||||||
// e.target.value: current value of the textarea
|
// e.target.value: current value of the textarea
|
||||||
const [start, word] = suggestText(e.target.value, e.target.selectionStart)
|
const [start, word] = suggestText(target.value, target.selectionStart!)
|
||||||
if (!start || !word) {
|
if (!start || !word) {
|
||||||
this.closeSuggest()
|
closeSuggest()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch (word.charAt(0)) {
|
switch (word.charAt(0)) {
|
||||||
case ':':
|
case ':':
|
||||||
await this.suggestEmoji(start, word)
|
await suggestEmoji(start, word)
|
||||||
return true
|
return true
|
||||||
case '@':
|
case '@':
|
||||||
await this.suggestAccount(start, word)
|
await suggestAccount(start, word)
|
||||||
return true
|
return true
|
||||||
case '#':
|
case '#':
|
||||||
await this.suggestHashtag(start, word)
|
await suggestHashtag(start, word)
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
async suggestAccount(start, word) {
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch('TimelineSpace/Modals/NewToot/Status/suggestAccount', { word: word, start: start })
|
|
||||||
this.$emit('suggestOpened', true)
|
|
||||||
return true
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
},
|
const startSuggest = (e: Event) => {
|
||||||
async suggestHashtag(start, word) {
|
const currentValue = (e.target as HTMLInputElement).value
|
||||||
try {
|
// Start suggest after user stop writing
|
||||||
await this.$store.dispatch('TimelineSpace/Modals/NewToot/Status/suggestHashtag', { word: word, start: start })
|
setTimeout(async () => {
|
||||||
this.$emit('suggestOpened', true)
|
if (currentValue === modelValue.value) {
|
||||||
return true
|
await suggest(e)
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
},
|
}, 700)
|
||||||
suggestEmoji(start, word) {
|
|
||||||
try {
|
|
||||||
this.$store.dispatch('TimelineSpace/Modals/NewToot/Status/suggestEmoji', { word: word, start: start })
|
|
||||||
this.$emit('suggestOpened', true)
|
|
||||||
return true
|
|
||||||
} catch (err) {
|
|
||||||
this.closeSuggest()
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
closeSuggest() {
|
const suggestHighlight = (index: number) => {
|
||||||
this.$store.dispatch('TimelineSpace/Modals/NewToot/Status/closeSuggest')
|
|
||||||
if (this.openSuggest) {
|
|
||||||
this.highlightedIndex = 0
|
|
||||||
}
|
|
||||||
this.$emit('suggestOpened', false)
|
|
||||||
},
|
|
||||||
suggestHighlight(index) {
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
this.highlightedIndex = 0
|
highlightedIndex.value = 0
|
||||||
} else if (index >= this.filteredSuggestion.length) {
|
} else if (index >= filteredSuggestion.value.length) {
|
||||||
this.highlightedIndex = this.filteredSuggestion.length - 1
|
highlightedIndex.value = filteredSuggestion.value.length - 1
|
||||||
} else {
|
} else {
|
||||||
this.highlightedIndex = index
|
highlightedIndex.value = index
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
insertItem(item) {
|
const insertItem = item => {
|
||||||
|
console.log('inserted', item.name)
|
||||||
if (item.code) {
|
if (item.code) {
|
||||||
const str = `${this.status.slice(0, this.startIndex - 1)}${item.code} ${this.status.slice(this.startIndex + this.matchWord.length)}`
|
const str = `${modelValue.value.slice(0, startIndex.value - 1)}${item.code} ${modelValue.value.slice(
|
||||||
this.status = str
|
startIndex.value + matchWord.value.length
|
||||||
|
)}`
|
||||||
|
ctx.emit('update:modelValue', str)
|
||||||
} else {
|
} else {
|
||||||
const str = `${this.status.slice(0, this.startIndex - 1)}${item.name} ${this.status.slice(this.startIndex + this.matchWord.length)}`
|
const str = `${modelValue.value.slice(0, startIndex.value - 1)}${item.name} ${modelValue.value.slice(
|
||||||
this.status = str
|
startIndex.value + matchWord.value.length
|
||||||
|
)}`
|
||||||
|
console.log(str)
|
||||||
|
ctx.emit('update:modelValue', str)
|
||||||
}
|
}
|
||||||
this.closeSuggest()
|
closeSuggest()
|
||||||
},
|
|
||||||
selectCurrentItem() {
|
|
||||||
const item = this.filteredSuggestion[this.highlightedIndex]
|
|
||||||
this.insertItem(item)
|
|
||||||
},
|
|
||||||
onPaste(e) {
|
|
||||||
this.$emit('paste', e)
|
|
||||||
},
|
|
||||||
handleKey(event) {
|
|
||||||
const current = event.target.selectionStart
|
|
||||||
switch (event.srcKey) {
|
|
||||||
case 'up':
|
|
||||||
this.suggestHighlight(this.highlightedIndex - 1)
|
|
||||||
break
|
|
||||||
case 'down':
|
|
||||||
this.suggestHighlight(this.highlightedIndex + 1)
|
|
||||||
break
|
|
||||||
case 'enter':
|
|
||||||
this.selectCurrentItem()
|
|
||||||
break
|
|
||||||
case 'esc':
|
|
||||||
this.closeSuggest()
|
|
||||||
break
|
|
||||||
case 'left':
|
|
||||||
event.target.setSelectionRange(current - 1, current - 1)
|
|
||||||
break
|
|
||||||
case 'right':
|
|
||||||
event.target.setSelectionRange(current + 1, current + 1)
|
|
||||||
break
|
|
||||||
case 'linux':
|
|
||||||
case 'mac':
|
|
||||||
this.$emit('toot')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
},
|
const selectEmoji = emoji => {
|
||||||
toggleEmojiPicker() {
|
const current = statusRef.value?.selectionStart
|
||||||
this.openEmojiPicker = !this.openEmojiPicker
|
|
||||||
this.$emit('pickerOpened', this.openEmojiPicker)
|
|
||||||
},
|
|
||||||
selectEmoji(emoji) {
|
|
||||||
const current = this.$refs.status.selectionStart
|
|
||||||
if (emoji.native) {
|
if (emoji.native) {
|
||||||
this.status = `${this.status.slice(0, current)}${emoji.native} ${this.status.slice(current)}`
|
ctx.emit('update:modelValue', `${modelValue.value.slice(0, current)}${emoji.native} ${modelValue.value.slice(current)}`)
|
||||||
} else {
|
} else {
|
||||||
// Custom emoji don't have natvie code
|
// Custom emoji don't have natvie code
|
||||||
this.status = `${this.status.slice(0, current)}${emoji.name} ${this.status.slice(current)}`
|
ctx.emit('update:modelValue', `${modelValue.value.slice(0, current)}${emoji.name} ${modelValue.value.slice(current)}`)
|
||||||
}
|
|
||||||
this.hideEmojiPicker()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
emojiIndex,
|
||||||
|
highlightedIndex,
|
||||||
|
filteredAccounts,
|
||||||
|
filteredHashtags,
|
||||||
|
filteredSuggestion,
|
||||||
|
pickerEmojis,
|
||||||
|
openSuggest,
|
||||||
|
startSuggest,
|
||||||
|
suggestHighlight,
|
||||||
|
insertItem,
|
||||||
|
selectEmoji
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import emojidata from 'unicode-emoji-json/data-by-emoji.json'
|
import { EmojiIndex } from 'emoji-mart-vue-fast'
|
||||||
|
import emojidata from 'emoji-mart-vue-fast/data/all.json'
|
||||||
import generator, { MegalodonInterface } from 'megalodon'
|
import generator, { MegalodonInterface } from 'megalodon'
|
||||||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
|
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
|
||||||
import { RootState } from '@/store/index'
|
import { RootState } from '@/store/index'
|
||||||
|
@ -7,7 +8,19 @@ import { InsertAccountCache } from '~/src/types/insertAccountCache'
|
||||||
import { CachedAccount } from '~/src/types/cachedAccount'
|
import { CachedAccount } from '~/src/types/cachedAccount'
|
||||||
import { MyWindow } from '~/src/types/global'
|
import { MyWindow } from '~/src/types/global'
|
||||||
|
|
||||||
const win = (window as any) as MyWindow
|
const win = window as any as MyWindow
|
||||||
|
|
||||||
|
const emojiIndex = new EmojiIndex(emojidata)
|
||||||
|
|
||||||
|
type EmojiMartEmoji = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
colons: string
|
||||||
|
text: string
|
||||||
|
emoticons: Array<string>
|
||||||
|
skin: any
|
||||||
|
native: string
|
||||||
|
}
|
||||||
|
|
||||||
type Suggest = {
|
type Suggest = {
|
||||||
name: string
|
name: string
|
||||||
|
@ -27,8 +40,8 @@ export type StatusState = {
|
||||||
filteredHashtags: Array<SuggestHashtag>
|
filteredHashtags: Array<SuggestHashtag>
|
||||||
filteredEmojis: Array<SuggestEmoji>
|
filteredEmojis: Array<SuggestEmoji>
|
||||||
openSuggest: boolean
|
openSuggest: boolean
|
||||||
startIndex: number | null
|
startIndex: number
|
||||||
matchWord: string | null
|
matchWord: string
|
||||||
client: MegalodonInterface | null
|
client: MegalodonInterface | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +51,8 @@ const state = (): StatusState => ({
|
||||||
filteredHashtags: [],
|
filteredHashtags: [],
|
||||||
filteredEmojis: [],
|
filteredEmojis: [],
|
||||||
openSuggest: false,
|
openSuggest: false,
|
||||||
startIndex: null,
|
startIndex: 0,
|
||||||
matchWord: null,
|
matchWord: '',
|
||||||
client: null
|
client: null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -113,10 +126,10 @@ const mutations: MutationTree<StatusState> = {
|
||||||
[MUTATION_TYPES.CHANGE_OPEN_SUGGEST]: (state, value: boolean) => {
|
[MUTATION_TYPES.CHANGE_OPEN_SUGGEST]: (state, value: boolean) => {
|
||||||
state.openSuggest = value
|
state.openSuggest = value
|
||||||
},
|
},
|
||||||
[MUTATION_TYPES.CHANGE_START_INDEX]: (state, index: number | null) => {
|
[MUTATION_TYPES.CHANGE_START_INDEX]: (state, index: number) => {
|
||||||
state.startIndex = index
|
state.startIndex = index
|
||||||
},
|
},
|
||||||
[MUTATION_TYPES.CHANGE_MATCH_WORD]: (state, word: string | null) => {
|
[MUTATION_TYPES.CHANGE_MATCH_WORD]: (state, word: string) => {
|
||||||
state.matchWord = word
|
state.matchWord = word
|
||||||
},
|
},
|
||||||
[MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS]: state => {
|
[MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS]: state => {
|
||||||
|
@ -144,8 +157,16 @@ type WordStart = {
|
||||||
start: number
|
start: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ACTION_TYPES = {
|
||||||
|
SUGGEST_ACCOUNT: 'suggestAccount',
|
||||||
|
SUGGEST_HASHTAG: 'suggestHashtag',
|
||||||
|
SUGGEST_EMOJI: 'suggestEmoji',
|
||||||
|
CANCEL_REQUEST: 'cancelRequest',
|
||||||
|
CLOSE_SUGGEST: 'closeSuggest'
|
||||||
|
}
|
||||||
|
|
||||||
const actions: ActionTree<StatusState, RootState> = {
|
const actions: ActionTree<StatusState, RootState> = {
|
||||||
suggestAccount: async ({ commit, rootState, dispatch }, wordStart: WordStart) => {
|
[ACTION_TYPES.SUGGEST_ACCOUNT]: async ({ commit, rootState, dispatch }, wordStart: WordStart) => {
|
||||||
dispatch('cancelRequest')
|
dispatch('cancelRequest')
|
||||||
commit(MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS)
|
commit(MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS)
|
||||||
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS)
|
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS)
|
||||||
|
@ -188,7 +209,7 @@ const actions: ActionTree<StatusState, RootState> = {
|
||||||
}
|
}
|
||||||
await Promise.all([searchCache(), searchAPI()])
|
await Promise.all([searchCache(), searchAPI()])
|
||||||
},
|
},
|
||||||
suggestHashtag: async ({ commit, rootState, dispatch }, wordStart: WordStart) => {
|
[ACTION_TYPES.SUGGEST_HASHTAG]: async ({ commit, rootState, dispatch }, wordStart: WordStart) => {
|
||||||
dispatch('cancelRequest')
|
dispatch('cancelRequest')
|
||||||
commit(MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS)
|
commit(MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS)
|
||||||
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS)
|
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS)
|
||||||
|
@ -231,16 +252,29 @@ const actions: ActionTree<StatusState, RootState> = {
|
||||||
}
|
}
|
||||||
await Promise.all([searchCache(), searchAPI()])
|
await Promise.all([searchCache(), searchAPI()])
|
||||||
},
|
},
|
||||||
suggestEmoji: ({ commit, rootState }, wordStart: WordStart) => {
|
[ACTION_TYPES.SUGGEST_EMOJI]: ({ commit, rootState }, wordStart: WordStart) => {
|
||||||
const { word, start } = wordStart
|
const { word, start } = wordStart
|
||||||
// Find native emojis
|
// Find native emojis
|
||||||
const filteredEmojiName: Array<string> = Object.keys(emojidata).filter((emoji: string) => `:${emojidata[emoji].name}:`.includes(word))
|
const foundEmoji: EmojiMartEmoji = emojiIndex.findEmoji(word)
|
||||||
const filteredNativeEmoji: Array<SuggestEmoji> = filteredEmojiName.map((emoji: string) => {
|
if (foundEmoji) {
|
||||||
return {
|
return {
|
||||||
name: `:${emojidata[emoji].name}:`,
|
name: foundEmoji.colons,
|
||||||
code: emoji
|
code: foundEmoji.native
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let filteredNativeEmoji: Array<SuggestEmoji> = []
|
||||||
|
|
||||||
|
const regexp = word.match(/^:(.+)/)
|
||||||
|
if (regexp && regexp.length > 1) {
|
||||||
|
const emojiName = regexp[1]
|
||||||
|
const filteredEmoji: Array<EmojiMartEmoji> = emojiIndex.search(emojiName)
|
||||||
|
filteredNativeEmoji = filteredEmoji.map((emoji: EmojiMartEmoji) => {
|
||||||
|
return {
|
||||||
|
name: emoji.colons,
|
||||||
|
code: emoji.native
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
// Find custom emojis
|
// Find custom emojis
|
||||||
const filteredCustomEmoji: Array<Suggest> = rootState.TimelineSpace.emojis
|
const filteredCustomEmoji: Array<Suggest> = rootState.TimelineSpace.emojis
|
||||||
.map(emoji => {
|
.map(emoji => {
|
||||||
|
@ -264,16 +298,16 @@ const actions: ActionTree<StatusState, RootState> = {
|
||||||
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_EMOJIS)
|
commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_EMOJIS)
|
||||||
return filtered
|
return filtered
|
||||||
},
|
},
|
||||||
cancelRequest: ({ state }) => {
|
[ACTION_TYPES.CANCEL_REQUEST]: ({ state }) => {
|
||||||
if (state.client) {
|
if (state.client) {
|
||||||
state.client.cancel()
|
state.client.cancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeSuggest: ({ commit, dispatch }) => {
|
[ACTION_TYPES.CLOSE_SUGGEST]: ({ commit, dispatch }) => {
|
||||||
dispatch('cancelRequest')
|
dispatch('cancelRequest')
|
||||||
commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, false)
|
commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, false)
|
||||||
commit(MUTATION_TYPES.CHANGE_START_INDEX, null)
|
commit(MUTATION_TYPES.CHANGE_START_INDEX, 0)
|
||||||
commit(MUTATION_TYPES.CHANGE_MATCH_WORD, null)
|
commit(MUTATION_TYPES.CHANGE_MATCH_WORD, '')
|
||||||
commit(MUTATION_TYPES.CLEAR_FILTERED_SUGGESTION)
|
commit(MUTATION_TYPES.CLEAR_FILTERED_SUGGESTION)
|
||||||
commit(MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS)
|
commit(MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS)
|
||||||
commit(MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS)
|
commit(MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS)
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
// https://github.com/tootsuite/mastodon/blob/master/app/javascript/mastodon/components/autosuggest_textarea.js
|
// https://github.com/tootsuite/mastodon/blob/master/app/javascript/mastodon/components/autosuggest_textarea.js
|
||||||
const textAtCursorMatch = (str, cursorPosition, separators = ['@', '#', ':']) => {
|
const textAtCursorMatch = (
|
||||||
let word
|
str: string,
|
||||||
|
cursorPosition: number,
|
||||||
|
separators: Array<string> = ['@', '#', ':']
|
||||||
|
): [number | null, string | null] => {
|
||||||
|
let word: string
|
||||||
|
|
||||||
const left = str.slice(0, cursorPosition).search(/\S+$/)
|
const left = str.slice(0, cursorPosition).search(/\S+$/)
|
||||||
const right = str.slice(cursorPosition).search(/\s/)
|
const right = str.slice(cursorPosition).search(/\s/)
|
Loading…
Reference in New Issue