forked from AkkomaGang/akkoma-fe
develop #5
40 changed files with 553 additions and 300 deletions
|
@ -4,8 +4,6 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
||||||
<title>Akkoma</title>
|
<title>Akkoma</title>
|
||||||
<link rel="stylesheet" href="/static/font/css/fontello.css">
|
|
||||||
<link rel="stylesheet" href="/static/font/css/animation.css">
|
|
||||||
<link rel="stylesheet" href="/static/font/tiresias.css">
|
<link rel="stylesheet" href="/static/font/tiresias.css">
|
||||||
<link rel="stylesheet" href="/static/font/css/lato.css">
|
<link rel="stylesheet" href="/static/font/css/lato.css">
|
||||||
<link rel="stylesheet" href="/static/mfm.css">
|
<link rel="stylesheet" href="/static/mfm.css">
|
||||||
|
|
|
@ -322,6 +322,8 @@ const getNodeInfo = async ({ store }) => {
|
||||||
: federation.enabled
|
: federation.enabled
|
||||||
})
|
})
|
||||||
|
|
||||||
|
store.dispatch('setInstanceOption', { name: 'publicTimelineVisibility', value: metadata.publicTimelineVisibility })
|
||||||
|
|
||||||
const accountActivationRequired = metadata.accountActivationRequired
|
const accountActivationRequired = metadata.accountActivationRequired
|
||||||
store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
|
store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
|
||||||
|
|
||||||
|
@ -396,9 +398,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
])
|
])
|
||||||
|
|
||||||
// Start fetching things that don't need to block the UI
|
// Start fetching things that don't need to block the UI
|
||||||
store.dispatch('fetchMutes')
|
|
||||||
store.dispatch('startFetchingAnnouncements')
|
|
||||||
store.dispatch('startFetchingReports')
|
|
||||||
getTOS({ store })
|
getTOS({ store })
|
||||||
getStickers({ store })
|
getStickers({ store })
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
faInfoCircle,
|
faInfoCircle,
|
||||||
faUserTie
|
faUserTie
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
faSignInAlt,
|
faSignInAlt,
|
||||||
|
@ -103,7 +104,10 @@ export default {
|
||||||
},
|
},
|
||||||
showBubbleTimeline () {
|
showBubbleTimeline () {
|
||||||
return this.$store.state.instance.localBubbleInstances.length > 0
|
return this.$store.state.instance.localBubbleInstances.length > 0
|
||||||
}
|
},
|
||||||
|
...mapState({
|
||||||
|
publicTimelineVisibility: state => state.instance.publicTimelineVisibility,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
scrollToTop () {
|
scrollToTop () {
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'public-timeline' }"
|
:to="{ name: 'public-timeline' }"
|
||||||
class="nav-icon"
|
class="nav-icon"
|
||||||
|
v-if="(currentUser || (publicTimelineVisibility?.local ?? true))"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
|
@ -69,6 +70,7 @@
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'public-external-timeline' }"
|
:to="{ name: 'public-external-timeline' }"
|
||||||
class="nav-icon"
|
class="nav-icon"
|
||||||
|
v-if="(currentUser || (publicTimelineVisibility?.federated ?? true))"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
|
|
133
src/components/emoji_grid/emoji_grid.js
Normal file
133
src/components/emoji_grid/emoji_grid.js
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
const EMOJI_SIZE = 32 + 8
|
||||||
|
const GROUP_TITLE_HEIGHT = 24
|
||||||
|
const BUFFER_SIZE = 3 * EMOJI_SIZE
|
||||||
|
|
||||||
|
const EmojiGrid = {
|
||||||
|
props: {
|
||||||
|
groups: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
containerWidth: 0,
|
||||||
|
containerHeight: 0,
|
||||||
|
scrollPos: 0,
|
||||||
|
resizeObserver: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
const rect = this.$refs.container.getBoundingClientRect()
|
||||||
|
this.containerWidth = rect.width
|
||||||
|
this.containerHeight = rect.height
|
||||||
|
this.resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
this.containerWidth = entry.contentRect.width
|
||||||
|
this.containerHeight = entry.contentRect.height
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.resizeObserver.observe(this.$refs.container)
|
||||||
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
this.resizeObserver.disconnect()
|
||||||
|
this.resizeObserver = null
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
groups () {
|
||||||
|
// Scroll to top when grid content changes
|
||||||
|
if (this.$refs.container) {
|
||||||
|
this.$refs.container.scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activeGroup (group) {
|
||||||
|
this.$emit('activeGroup', group)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onScroll () {
|
||||||
|
this.scrollPos = this.$refs.container.scrollTop
|
||||||
|
},
|
||||||
|
onEmoji (emoji) {
|
||||||
|
this.$emit('emoji', emoji)
|
||||||
|
},
|
||||||
|
scrollToItem (itemId) {
|
||||||
|
const container = this.$refs.container
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
for (const item of this.itemList) {
|
||||||
|
if (item.id === itemId) {
|
||||||
|
container.scrollTo(0, item.position.y)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// Total height of scroller content
|
||||||
|
gridHeight () {
|
||||||
|
if (this.itemList.length === 0) return 0
|
||||||
|
const lastItem = this.itemList[this.itemList.length - 1]
|
||||||
|
return (
|
||||||
|
lastItem.position.y +
|
||||||
|
('title' in lastItem ? GROUP_TITLE_HEIGHT : EMOJI_SIZE)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
activeGroup () {
|
||||||
|
const items = this.itemList
|
||||||
|
for (let i = items.length - 1; i >= 0; i--) {
|
||||||
|
const item = items[i]
|
||||||
|
if ('title' in item && item.position.y <= this.scrollPos) {
|
||||||
|
return item.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
itemList () {
|
||||||
|
const items = []
|
||||||
|
let x = 0
|
||||||
|
let y = 0
|
||||||
|
for (const group of this.groups) {
|
||||||
|
items.push({ position: { x, y }, id: group.id, title: group.text })
|
||||||
|
if (group.text.length) {
|
||||||
|
y += GROUP_TITLE_HEIGHT
|
||||||
|
}
|
||||||
|
for (const emoji of group.emojis) {
|
||||||
|
items.push({
|
||||||
|
position: { x, y },
|
||||||
|
id: `${group.id}-${emoji.displayText}`,
|
||||||
|
emoji
|
||||||
|
})
|
||||||
|
x += EMOJI_SIZE
|
||||||
|
if (x + EMOJI_SIZE > this.containerWidth) {
|
||||||
|
y += EMOJI_SIZE
|
||||||
|
x = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (x > 0) {
|
||||||
|
y += EMOJI_SIZE
|
||||||
|
x = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
visibleItems () {
|
||||||
|
const startPos = this.scrollPos - BUFFER_SIZE
|
||||||
|
const endPos = this.scrollPos + this.containerHeight + BUFFER_SIZE
|
||||||
|
return this.itemList.filter((i) => {
|
||||||
|
return i.position.y >= startPos && i.position.y < endPos
|
||||||
|
})
|
||||||
|
},
|
||||||
|
scrolledClass () {
|
||||||
|
if (this.scrollPos <= 5) {
|
||||||
|
return 'scrolled-top'
|
||||||
|
} else if (this.scrollPos >= this.gridHeight - this.containerHeight - 5) {
|
||||||
|
return 'scrolled-bottom'
|
||||||
|
} else {
|
||||||
|
return 'scrolled-middle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmojiGrid
|
60
src/components/emoji_grid/emoji_grid.scss
Normal file
60
src/components/emoji_grid/emoji_grid.scss
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
.emoji {
|
||||||
|
&-grid {
|
||||||
|
flex: 1 1 1px;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
user-select: none;
|
||||||
|
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
||||||
|
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
|
||||||
|
linear-gradient(to top, white, white);
|
||||||
|
transition: mask-size 150ms;
|
||||||
|
mask-size: 100% 20px, 100% 20px, auto;
|
||||||
|
// Autoprefixed seem to ignore this one, and also syntax is different
|
||||||
|
-webkit-mask-composite: xor;
|
||||||
|
mask-composite: exclude;
|
||||||
|
&.scrolled {
|
||||||
|
&-top {
|
||||||
|
mask-size: 100% 20px, 100% 0, auto;
|
||||||
|
}
|
||||||
|
&-bottom {
|
||||||
|
mask-size: 100% 0, 100% 20px, auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
margin-left: 5px;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-group-title {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 0.85em;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
font-size: 32px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 4px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: contain;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/components/emoji_grid/emoji_grid.vue
Normal file
48
src/components/emoji_grid/emoji_grid.vue
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="container"
|
||||||
|
class="emoji-grid"
|
||||||
|
:class="scrolledClass"
|
||||||
|
@scroll.passive="onScroll"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
height: `${gridHeight}px`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template v-for="item in visibleItems">
|
||||||
|
<h6
|
||||||
|
v-if="'title' in item && item.title.length"
|
||||||
|
:key="'title-' + item.id"
|
||||||
|
class="emoji-group-title"
|
||||||
|
:style="{
|
||||||
|
top: item.position.y + 'px',
|
||||||
|
left: item.position.x + 'px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</h6>
|
||||||
|
<span
|
||||||
|
v-else-if="'emoji' in item"
|
||||||
|
:key="'emoji-' + item.id"
|
||||||
|
class="emoji-item"
|
||||||
|
:title="item.emoji.displayText"
|
||||||
|
:style="{
|
||||||
|
top: item.position.y + 'px',
|
||||||
|
left: item.position.x + 'px'
|
||||||
|
}"
|
||||||
|
@click.stop.prevent="onEmoji(item.emoji)"
|
||||||
|
>
|
||||||
|
<span v-if="!item.emoji.imageUrl">{{ item.emoji.replacement }}</span>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
:src="item.emoji.imageUrl"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./emoji_grid.js"></script>
|
||||||
|
<style lang="scss" src="./emoji_grid.scss"></style>
|
|
@ -205,7 +205,6 @@ const EmojiInput = {
|
||||||
},
|
},
|
||||||
triggerShowPicker () {
|
triggerShowPicker () {
|
||||||
this.showPicker = true
|
this.showPicker = true
|
||||||
this.$refs.picker.startEmojiLoad()
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.scrollIntoView()
|
this.scrollIntoView()
|
||||||
this.focusPickerInput()
|
this.focusPickerInput()
|
||||||
|
@ -223,7 +222,6 @@ const EmojiInput = {
|
||||||
this.showPicker = !this.showPicker
|
this.showPicker = !this.showPicker
|
||||||
if (this.showPicker) {
|
if (this.showPicker) {
|
||||||
this.scrollIntoView()
|
this.scrollIntoView()
|
||||||
this.$refs.picker.startEmojiLoad()
|
|
||||||
this.$nextTick(this.focusPickerInput)
|
this.$nextTick(this.focusPickerInput)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
import Checkbox from '../checkbox/checkbox.vue'
|
import Checkbox from '../checkbox/checkbox.vue'
|
||||||
|
import EmojiGrid from '../emoji_grid/emoji_grid.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faBoxOpen,
|
faBoxOpen,
|
||||||
|
@ -14,13 +15,6 @@ library.add(
|
||||||
faSmileBeam
|
faSmileBeam
|
||||||
)
|
)
|
||||||
|
|
||||||
// At widest, approximately 20 emoji are visible in a row,
|
|
||||||
// loading 3 rows, could be overkill for narrow picker
|
|
||||||
const LOAD_EMOJI_BY = 60
|
|
||||||
|
|
||||||
// When to start loading new batch emoji, in pixels
|
|
||||||
const LOAD_EMOJI_MARGIN = 64
|
|
||||||
|
|
||||||
const EmojiPicker = {
|
const EmojiPicker = {
|
||||||
props: {
|
props: {
|
||||||
enableStickerPicker: {
|
enableStickerPicker: {
|
||||||
|
@ -39,16 +33,13 @@ const EmojiPicker = {
|
||||||
keyword: '',
|
keyword: '',
|
||||||
activeGroup: 'standard',
|
activeGroup: 'standard',
|
||||||
showingStickers: false,
|
showingStickers: false,
|
||||||
groupsScrolledClass: 'scrolled-top',
|
keepOpen: false
|
||||||
keepOpen: false,
|
|
||||||
customEmojiBufferSlice: LOAD_EMOJI_BY,
|
|
||||||
customEmojiTimeout: null,
|
|
||||||
customEmojiLoadAllConfirmed: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
|
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
|
||||||
Checkbox
|
Checkbox,
|
||||||
|
EmojiGrid
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onStickerUploaded (e) {
|
onStickerUploaded (e) {
|
||||||
|
@ -60,12 +51,7 @@ const EmojiPicker = {
|
||||||
onEmoji (emoji) {
|
onEmoji (emoji) {
|
||||||
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
|
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
|
||||||
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
||||||
},
|
this.$store.commit('emojiUsed', emoji)
|
||||||
onScroll (e) {
|
|
||||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
|
||||||
this.updateScrolledClass(target)
|
|
||||||
this.scrolledGroup(target)
|
|
||||||
this.triggerLoadMore(target)
|
|
||||||
},
|
},
|
||||||
onWheel (e) {
|
onWheel (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -74,68 +60,12 @@ const EmojiPicker = {
|
||||||
highlight (key) {
|
highlight (key) {
|
||||||
this.setShowStickers(false)
|
this.setShowStickers(false)
|
||||||
this.activeGroup = key
|
this.activeGroup = key
|
||||||
},
|
if (this.keyword.length) {
|
||||||
updateScrolledClass (target) {
|
this.$refs.emojiGrid.scrollToItem(key)
|
||||||
if (target.scrollTop <= 5) {
|
|
||||||
this.groupsScrolledClass = 'scrolled-top'
|
|
||||||
} else if (target.scrollTop >= target.scrollTopMax - 5) {
|
|
||||||
this.groupsScrolledClass = 'scrolled-bottom'
|
|
||||||
} else {
|
|
||||||
this.groupsScrolledClass = 'scrolled-middle'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
triggerLoadMore (target) {
|
onActiveGroup (group) {
|
||||||
const ref = this.$refs['group-end-custom']
|
this.activeGroup = group
|
||||||
if (!ref) return
|
|
||||||
const bottom = ref.offsetTop + ref.offsetHeight
|
|
||||||
|
|
||||||
const scrollerBottom = target.scrollTop + target.clientHeight
|
|
||||||
const scrollerTop = target.scrollTop
|
|
||||||
const scrollerMax = target.scrollHeight
|
|
||||||
|
|
||||||
// Loads more emoji when they come into view
|
|
||||||
const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
|
|
||||||
// Always load when at the very top in case there's no scroll space yet
|
|
||||||
const atTop = scrollerTop < 5
|
|
||||||
// Don't load when looking at unicode category or at the very bottom
|
|
||||||
const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax
|
|
||||||
if (!bottomAboveViewport && (approachingBottom || atTop)) {
|
|
||||||
this.loadEmoji()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrolledGroup (target) {
|
|
||||||
const top = target.scrollTop + 5
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.emojisView.forEach(group => {
|
|
||||||
const ref = this.$refs['group-' + group.id]
|
|
||||||
if (ref.offsetTop <= top) {
|
|
||||||
this.activeGroup = group.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
loadEmoji () {
|
|
||||||
const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
|
|
||||||
|
|
||||||
if (allLoaded) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.customEmojiBufferSlice += LOAD_EMOJI_BY
|
|
||||||
},
|
|
||||||
startEmojiLoad (forceUpdate = false) {
|
|
||||||
if (!forceUpdate) {
|
|
||||||
this.keyword = ''
|
|
||||||
}
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs['emoji-groups'].scrollTop = 0
|
|
||||||
})
|
|
||||||
const bufferSize = this.customEmojiBuffer.length
|
|
||||||
const bufferPrefilledAll = bufferSize === this.filteredEmoji.length
|
|
||||||
if (bufferPrefilledAll && !forceUpdate) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.customEmojiBufferSlice = LOAD_EMOJI_BY
|
|
||||||
},
|
},
|
||||||
toggleStickers () {
|
toggleStickers () {
|
||||||
this.showingStickers = !this.showingStickers
|
this.showingStickers = !this.showingStickers
|
||||||
|
@ -151,13 +81,6 @@ const EmojiPicker = {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
keyword () {
|
|
||||||
this.customEmojiLoadAllConfirmed = false
|
|
||||||
this.onScroll()
|
|
||||||
this.startEmojiLoad(true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
activeGroupView () {
|
activeGroupView () {
|
||||||
return this.showingStickers ? '' : this.activeGroup
|
return this.showingStickers ? '' : this.activeGroup
|
||||||
|
@ -173,10 +96,8 @@ const EmojiPicker = {
|
||||||
this.$store.state.instance.customEmoji || []
|
this.$store.state.instance.customEmoji || []
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
customEmojiBuffer () {
|
|
||||||
return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
|
|
||||||
},
|
|
||||||
emojis () {
|
emojis () {
|
||||||
|
const recentEmojis = this.$store.getters.recentEmojis
|
||||||
const standardEmojis = this.$store.state.instance.emoji || []
|
const standardEmojis = this.$store.state.instance.emoji || []
|
||||||
const customEmojis = this.sortedEmoji
|
const customEmojis = this.sortedEmoji
|
||||||
const emojiPacks = []
|
const emojiPacks = []
|
||||||
|
@ -189,6 +110,15 @@ const EmojiPicker = {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
id: 'recent',
|
||||||
|
text: this.$t('emoji.recent'),
|
||||||
|
first: {
|
||||||
|
imageUrl: '',
|
||||||
|
replacement: '🕒',
|
||||||
|
},
|
||||||
|
emojis: this.filterByKeyword(recentEmojis)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'standard',
|
id: 'standard',
|
||||||
text: this.$t('emoji.unicode'),
|
text: this.$t('emoji.unicode'),
|
||||||
|
|
|
@ -85,10 +85,6 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-groups {
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.additional-tabs {
|
.additional-tabs {
|
||||||
border-left: 1px solid;
|
border-left: 1px solid;
|
||||||
border-left-color: $fallback--icon;
|
border-left-color: $fallback--icon;
|
||||||
|
@ -167,8 +163,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji {
|
.emoji-search {
|
||||||
&-search {
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
@ -176,67 +171,4 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-groups {
|
|
||||||
flex: 1 1 1px;
|
|
||||||
position: relative;
|
|
||||||
overflow: auto;
|
|
||||||
user-select: none;
|
|
||||||
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
|
||||||
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
|
|
||||||
linear-gradient(to top, white, white);
|
|
||||||
transition: mask-size 150ms;
|
|
||||||
mask-size: 100% 20px, 100% 20px, auto;
|
|
||||||
// Autoprefixed seem to ignore this one, and also syntax is different
|
|
||||||
-webkit-mask-composite: xor;
|
|
||||||
mask-composite: exclude;
|
|
||||||
&.scrolled {
|
|
||||||
&-top {
|
|
||||||
mask-size: 100% 20px, 100% 0, auto;
|
|
||||||
}
|
|
||||||
&-bottom {
|
|
||||||
mask-size: 100% 0, 100% 20px, auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding-left: 5px;
|
|
||||||
justify-content: left;
|
|
||||||
|
|
||||||
&-title {
|
|
||||||
font-size: 0.85em;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-item {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
font-size: 32px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 4px;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
img {
|
|
||||||
object-fit: contain;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<div class="emoji-picker panel panel-default panel-body">
|
<div class="emoji-picker panel panel-default panel-body">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<span
|
<span
|
||||||
|
ref="emoji-tabs"
|
||||||
class="emoji-tabs"
|
class="emoji-tabs"
|
||||||
@wheel="onWheel"
|
@wheel="onWheel"
|
||||||
ref="emoji-tabs"
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-for="group in emojis"
|
v-for="group in emojis"
|
||||||
|
@ -51,39 +51,12 @@
|
||||||
@input="$event.target.composing = false"
|
@input="$event.target.composing = false"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<EmojiGrid
|
||||||
ref="emoji-groups"
|
ref="emojiGrid"
|
||||||
class="emoji-groups"
|
:groups="emojisView"
|
||||||
:class="groupsScrolledClass"
|
@emoji="onEmoji"
|
||||||
@scroll="onScroll"
|
@active-group="onActiveGroup"
|
||||||
>
|
/>
|
||||||
<div
|
|
||||||
v-for="group in emojisView"
|
|
||||||
:key="group.id"
|
|
||||||
class="emoji-group"
|
|
||||||
>
|
|
||||||
<h6
|
|
||||||
:ref="'group-' + group.id"
|
|
||||||
class="emoji-group-title"
|
|
||||||
>
|
|
||||||
{{ group.text }}
|
|
||||||
</h6>
|
|
||||||
<span
|
|
||||||
v-for="emoji in group.emojis"
|
|
||||||
:key="group.id + emoji.displayText"
|
|
||||||
:title="emoji.displayText"
|
|
||||||
class="emoji-item"
|
|
||||||
@click.stop.prevent="onEmoji(emoji)"
|
|
||||||
>
|
|
||||||
<span v-if="!emoji.imageUrl">{{ emoji.replacement }}</span>
|
|
||||||
<img
|
|
||||||
v-else
|
|
||||||
:src="emoji.imageUrl"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
<span :ref="'group-end-' + group.id" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-if="showKeepOpen"
|
v-if="showKeepOpen"
|
||||||
class="keep-open"
|
class="keep-open"
|
||||||
|
|
|
@ -3,6 +3,11 @@ import UserListPopover from '../user_list_popover/user_list_popover.vue'
|
||||||
|
|
||||||
const EMOJI_REACTION_COUNT_CUTOFF = 12
|
const EMOJI_REACTION_COUNT_CUTOFF = 12
|
||||||
|
|
||||||
|
const findEmojiByReplacement = (state, replacement) => {
|
||||||
|
const allEmojis = state.instance.emoji.concat(state.instance.customEmoji)
|
||||||
|
return allEmojis.find(emoji => emoji.replacement === replacement)
|
||||||
|
}
|
||||||
|
|
||||||
const EmojiReactions = {
|
const EmojiReactions = {
|
||||||
name: 'EmojiReactions',
|
name: 'EmojiReactions',
|
||||||
components: {
|
components: {
|
||||||
|
@ -54,6 +59,8 @@ const EmojiReactions = {
|
||||||
},
|
},
|
||||||
reactWith (emoji) {
|
reactWith (emoji) {
|
||||||
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
||||||
|
const emojiObject = findEmojiByReplacement(this.$store.state, emoji)
|
||||||
|
this.$store.commit('emojiUsed', emojiObject)
|
||||||
},
|
},
|
||||||
unreact (emoji) {
|
unreact (emoji) {
|
||||||
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
:src="reaction.url"
|
:src="reaction.url"
|
||||||
:title="reaction.name"
|
:title="reaction.name"
|
||||||
class="reaction-emoji"
|
class="reaction-emoji"
|
||||||
width="2.55em"
|
height="2.55em"
|
||||||
>
|
>
|
||||||
{{ reaction.count }}
|
{{ reaction.count }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -49,6 +49,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 0.25em;
|
margin-top: 0.25em;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
container-type: inline-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unicode-emoji {
|
.unicode-emoji {
|
||||||
|
@ -64,7 +65,9 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
.reaction-emoji {
|
.reaction-emoji {
|
||||||
width: 2.55em !important;
|
width: auto;
|
||||||
|
max-width: 96cqw;
|
||||||
|
height: 2.55em !important;
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
faBookmark as faBookmarkReg,
|
faBookmark as faBookmarkReg,
|
||||||
faFlag
|
faFlag
|
||||||
} from '@fortawesome/free-regular-svg-icons'
|
} from '@fortawesome/free-regular-svg-icons'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
faEllipsisH,
|
faEllipsisH,
|
||||||
|
@ -191,7 +192,7 @@ const ExtraButtons = {
|
||||||
isEdited () {
|
isEdited () {
|
||||||
return this.status.edited_at !== null
|
return this.status.edited_at !== null
|
||||||
},
|
},
|
||||||
editingAvailable () { return this.$store.state.instance.editingAvailable }
|
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ const FollowRequestCard = {
|
||||||
doApprove () {
|
doApprove () {
|
||||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||||
this.$store.dispatch('removeFollowRequest', this.user)
|
this.$store.dispatch('removeFollowRequest', this.user)
|
||||||
|
this.$store.dispatch('decrementFollowRequestsCount')
|
||||||
|
|
||||||
const notifId = this.findFollowRequestNotificationId()
|
const notifId = this.findFollowRequestNotificationId()
|
||||||
this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId })
|
this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId })
|
||||||
|
@ -66,6 +67,7 @@ const FollowRequestCard = {
|
||||||
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
||||||
|
this.$store.dispatch('decrementFollowRequestsCount')
|
||||||
this.$store.dispatch('removeFollowRequest', this.user)
|
this.$store.dispatch('removeFollowRequest', this.user)
|
||||||
})
|
})
|
||||||
this.hideDenyConfirmDialog()
|
this.hideDenyConfirmDialog()
|
||||||
|
@ -80,6 +82,11 @@ const FollowRequestCard = {
|
||||||
},
|
},
|
||||||
shouldConfirmDeny () {
|
shouldConfirmDeny () {
|
||||||
return this.mergedConfig.modalOnDenyFollow
|
return this.mergedConfig.modalOnDenyFollow
|
||||||
|
},
|
||||||
|
show () {
|
||||||
|
const notifId = this.$store.state.api.followRequests.find(req => req.id === this.user.id)
|
||||||
|
|
||||||
|
return notifId !== undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<basic-user-card :user="user">
|
<basic-user-card :user="user" v-if="show">
|
||||||
<div class="follow-request-card-content-container">
|
<div class="follow-request-card-content-container">
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
|
|
|
@ -1,10 +1,26 @@
|
||||||
import FollowRequestCard from '../follow_request_card/follow_request_card.vue'
|
import FollowRequestCard from '../follow_request_card/follow_request_card.vue'
|
||||||
|
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
||||||
|
import List from '../list/list.vue'
|
||||||
|
import get from 'lodash/get'
|
||||||
|
|
||||||
|
const FollowRequestList = withLoadMore({
|
||||||
|
fetch: (props, $store) => $store.dispatch('fetchFollowRequests'),
|
||||||
|
select: (props, $store) => get($store.state.api, 'followRequests', []).map(req => $store.getters.findUser(req.id)),
|
||||||
|
destroy: (props, $store) => $store.dispatch('clearFollowRequests'),
|
||||||
|
childPropName: 'items',
|
||||||
|
additionalPropNames: ['userId']
|
||||||
|
})(List);
|
||||||
|
|
||||||
|
|
||||||
const FollowRequests = {
|
const FollowRequests = {
|
||||||
components: {
|
components: {
|
||||||
FollowRequestCard
|
FollowRequestCard,
|
||||||
|
FollowRequestList
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
userId () {
|
||||||
|
return this.$store.state.users.currentUser.id
|
||||||
|
},
|
||||||
requests () {
|
requests () {
|
||||||
return this.$store.state.api.followRequests
|
return this.$store.state.api.followRequests
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<FollowRequestCard
|
<FollowRequestList :user-id="userId">
|
||||||
v-for="request in requests"
|
<template #item="{item}">
|
||||||
:key="request.id"
|
<FollowRequestCard :user="item" />
|
||||||
:user="request"
|
</template>
|
||||||
class="list-item"
|
</FollowRequestList>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -33,11 +33,6 @@ library.add(
|
||||||
)
|
)
|
||||||
|
|
||||||
const NavPanel = {
|
const NavPanel = {
|
||||||
created () {
|
|
||||||
if (this.currentUser && this.currentUser.locked) {
|
|
||||||
this.$store.dispatch('startFetchingFollowRequests')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
TimelineMenuContent
|
TimelineMenuContent
|
||||||
},
|
},
|
||||||
|
@ -54,11 +49,13 @@ const NavPanel = {
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
currentUser: state => state.users.currentUser,
|
currentUser: state => state.users.currentUser,
|
||||||
followRequestCount: state => state.api.followRequests.length,
|
|
||||||
privateMode: state => state.instance.private,
|
privateMode: state => state.instance.private,
|
||||||
federating: state => state.instance.federating
|
federating: state => state.instance.federating,
|
||||||
}),
|
}),
|
||||||
...mapGetters(['unreadAnnouncementCount'])
|
...mapGetters(['unreadAnnouncementCount']),
|
||||||
|
followRequestCount () {
|
||||||
|
return this.$store.state.users.currentUser.follow_requests_count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,14 @@ const pxStringToNumber = (str) => {
|
||||||
return Number(str.substring(0, str.length - 2))
|
return Number(str.substring(0, str.length - 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteDraft = (draftKey) => {
|
||||||
|
const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
|
||||||
|
|
||||||
|
delete draftData[draftKey];
|
||||||
|
|
||||||
|
localStorage.setItem('drafts', JSON.stringify(draftData));
|
||||||
|
}
|
||||||
|
|
||||||
const PostStatusForm = {
|
const PostStatusForm = {
|
||||||
props: [
|
props: [
|
||||||
'statusId',
|
'statusId',
|
||||||
|
@ -161,6 +169,34 @@ const PostStatusForm = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let draftKey = 'status';
|
||||||
|
if (this.replyTo) {
|
||||||
|
draftKey = 'reply:' + this.replyTo;
|
||||||
|
} else if (this.quoteId) {
|
||||||
|
draftKey = 'quote:' + this.quoteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey];
|
||||||
|
|
||||||
|
if (draft) {
|
||||||
|
statusParams = {
|
||||||
|
spoilerText: draft.data.spoilerText,
|
||||||
|
status: draft.data.status,
|
||||||
|
sensitiveIfSubject,
|
||||||
|
nsfw: draft.data.nsfw,
|
||||||
|
files: draft.data.files,
|
||||||
|
poll: draft.data.poll,
|
||||||
|
mediaDescriptions: draft.data.mediaDescriptions,
|
||||||
|
visibility: draft.data.visibility,
|
||||||
|
language: draft.data.language,
|
||||||
|
contentType: draft.data.contentType
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draft.data.poll) {
|
||||||
|
this.togglePollForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dropFiles: [],
|
dropFiles: [],
|
||||||
uploadingFiles: false,
|
uploadingFiles: false,
|
||||||
|
@ -280,6 +316,7 @@ const PostStatusForm = {
|
||||||
statusChanged () {
|
statusChanged () {
|
||||||
this.autoPreview()
|
this.autoPreview()
|
||||||
this.updateIdempotencyKey()
|
this.updateIdempotencyKey()
|
||||||
|
this.saveDraft()
|
||||||
},
|
},
|
||||||
clearStatus () {
|
clearStatus () {
|
||||||
const newStatus = this.newStatus
|
const newStatus = this.newStatus
|
||||||
|
@ -401,8 +438,38 @@ const PostStatusForm = {
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.previewLoading = false
|
this.previewLoading = false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let draftKey = 'status';
|
||||||
|
if (this.replyTo) {
|
||||||
|
draftKey = 'reply:' + this.replyTo;
|
||||||
|
} else if (this.quoteId) {
|
||||||
|
draftKey = 'quote:' + this.quoteId;
|
||||||
|
}
|
||||||
|
deleteDraft(draftKey)
|
||||||
},
|
},
|
||||||
debouncePreviewStatus: debounce(function () { this.previewStatus() }, 500),
|
debouncePreviewStatus: debounce(function () { this.previewStatus() }, 500),
|
||||||
|
saveDraft() {
|
||||||
|
const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
|
||||||
|
|
||||||
|
let draftKey = 'status';
|
||||||
|
if (this.replyTo) {
|
||||||
|
draftKey = 'reply:' + this.replyTo;
|
||||||
|
} else if (this.quoteId) {
|
||||||
|
draftKey = 'quote:' + this.quoteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.newStatus.status || this.newStatus.spoilerText || this.newStatus.files.length > 0 || this.newStatus.poll.length > 0) {
|
||||||
|
draftData[draftKey] = {
|
||||||
|
updatedAt: new Date(),
|
||||||
|
data: this.newStatus,
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem('drafts', JSON.stringify(draftData));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
deleteDraft(draftKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
autoPreview () {
|
autoPreview () {
|
||||||
if (!this.preview) return
|
if (!this.preview) return
|
||||||
this.previewLoading = true
|
this.previewLoading = true
|
||||||
|
|
|
@ -50,7 +50,6 @@
|
||||||
|
|
||||||
.emoji {
|
.emoji {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: var(--emoji-size, 32px);
|
|
||||||
height: var(--emoji-size, 32px);
|
height: var(--emoji-size, 32px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,21 +22,18 @@
|
||||||
|
|
||||||
._mfm_x2_ {
|
._mfm_x2_ {
|
||||||
.emoji {
|
.emoji {
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
._mfm_x3_ {
|
._mfm_x3_ {
|
||||||
.emoji {
|
.emoji {
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
._mfm_x4_ {
|
._mfm_x4_ {
|
||||||
.emoji {
|
.emoji {
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
|
|
||||||
img, video {
|
img, video {
|
||||||
&.emoji {
|
&.emoji {
|
||||||
width: 50px;
|
max-width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,6 @@
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
}
|
}
|
||||||
.emoji {
|
.emoji {
|
||||||
width: 32px !important;
|
|
||||||
height: 32px !important;
|
height: 32px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ const TimelineMenuContent = {
|
||||||
currentUser: state => state.users.currentUser,
|
currentUser: state => state.users.currentUser,
|
||||||
privateMode: state => state.instance.private,
|
privateMode: state => state.instance.private,
|
||||||
federating: state => state.instance.federating,
|
federating: state => state.instance.federating,
|
||||||
showBubbleTimeline: state => (state.instance.localBubbleInstances.length > 0)
|
showBubbleTimeline: state => (state.instance.localBubbleInstances.length > 0),
|
||||||
|
publicTimelineVisibility: state => state.instance.publicTimelineVisibility,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
>{{ $t("nav.bubble_timeline") }}</span>
|
>{{ $t("nav.bubble_timeline") }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="currentUser || !privateMode">
|
<li v-if="(currentUser || !privateMode) && (currentUser || (publicTimelineVisibility?.local ?? true))">
|
||||||
<router-link
|
<router-link
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
:to="{ name: 'public-timeline' }"
|
:to="{ name: 'public-timeline' }"
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
>{{ $t("nav.public_tl") }}</span>
|
>{{ $t("nav.public_tl") }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="federating && (currentUser || !privateMode)">
|
<li v-if="federating && (currentUser || !privateMode) && (currentUser || (publicTimelineVisibility?.federated ?? true))">
|
||||||
<router-link
|
<router-link
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
:to="{ name: 'public-external-timeline' }"
|
:to="{ name: 'public-external-timeline' }"
|
||||||
|
@ -62,6 +62,7 @@
|
||||||
:title="$t('nav.twkn_timeline_description')"
|
:title="$t('nav.twkn_timeline_description')"
|
||||||
:aria-label="$t('nav.twkn_timeline_description')"
|
:aria-label="$t('nav.twkn_timeline_description')"
|
||||||
>{{ $t("nav.twkn") }}</span>
|
>{{ $t("nav.twkn") }}</span>
|
||||||
|
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="currentUser">
|
<li v-if="currentUser">
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faChevronDown
|
faChevronDown
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
library.add(faChevronDown)
|
library.add(faChevronDown)
|
||||||
|
|
||||||
|
@ -41,7 +42,11 @@ const TimelineMenuTabs = {
|
||||||
},
|
},
|
||||||
privateMode () {
|
privateMode () {
|
||||||
return this.$store.state.instance.private
|
return this.$store.state.instance.private
|
||||||
}
|
},
|
||||||
|
...mapState({
|
||||||
|
currentUser: state => state.users.currentUser,
|
||||||
|
publicTimelineVisibility: state => state.instance.publicTimelineVisibility,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
timelineName () {
|
timelineName () {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'public-timeline' }"
|
:to="{ name: 'public-timeline' }"
|
||||||
class="nav-icon"
|
class="nav-icon"
|
||||||
|
v-if="currentUser || (publicTimelineVisibility?.local ?? true)"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'public-external-timeline' }"
|
:to="{ name: 'public-external-timeline' }"
|
||||||
class="nav-icon"
|
class="nav-icon"
|
||||||
|
v-if="currentUser || (publicTimelineVisibility?.federated ?? true)"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
|
|
|
@ -235,7 +235,7 @@
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.following, .requested_by {
|
.following, .requested_by, .blocking {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-bottom: .25em;
|
margin-bottom: .25em;
|
||||||
|
|
|
@ -127,6 +127,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-meta">
|
<div class="user-meta">
|
||||||
|
<div
|
||||||
|
v-if="relationship.blocked_by && loggedIn && isOtherUser"
|
||||||
|
class="blocking"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.blocks_you') }}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="relationship.followed_by && loggedIn && isOtherUser"
|
v-if="relationship.followed_by && loggedIn && isOtherUser"
|
||||||
class="following"
|
class="following"
|
||||||
|
@ -187,6 +193,7 @@
|
||||||
<FollowButton
|
<FollowButton
|
||||||
:relationship="relationship"
|
:relationship="relationship"
|
||||||
:user="user"
|
:user="user"
|
||||||
|
:disabled="relationship.blocked_by"
|
||||||
/>
|
/>
|
||||||
<template v-if="relationship.following">
|
<template v-if="relationship.following">
|
||||||
<ProgressButton
|
<ProgressButton
|
||||||
|
|
|
@ -224,7 +224,7 @@ const UserProfile = {
|
||||||
TabSwitcher,
|
TabSwitcher,
|
||||||
Conversation,
|
Conversation,
|
||||||
RichContent,
|
RichContent,
|
||||||
FollowedTagList,
|
FollowedTagList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,8 @@
|
||||||
"load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
|
"load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
|
||||||
"search_emoji": "Search for an emoji",
|
"search_emoji": "Search for an emoji",
|
||||||
"stickers": "Stickers",
|
"stickers": "Stickers",
|
||||||
"unicode": "Unicode emoji"
|
"unicode": "Unicode emoji",
|
||||||
|
"recent": "Recently used"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"storage_unavailable": "Pleroma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies."
|
"storage_unavailable": "Pleroma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies."
|
||||||
|
@ -1123,6 +1124,7 @@
|
||||||
"block_confirm_title": "Block user",
|
"block_confirm_title": "Block user",
|
||||||
"block_progress": "Blocking…",
|
"block_progress": "Blocking…",
|
||||||
"blocked": "Blocked!",
|
"blocked": "Blocked!",
|
||||||
|
"blocks_you": "Blocks you!",
|
||||||
"bot": "Bot",
|
"bot": "Bot",
|
||||||
"deactivated": "Deactivated",
|
"deactivated": "Deactivated",
|
||||||
"deny": "Deny",
|
"deny": "Deny",
|
||||||
|
|
|
@ -19,7 +19,8 @@ const saveImmedeatelyActions = [
|
||||||
'setOption',
|
'setOption',
|
||||||
'setClientData',
|
'setClientData',
|
||||||
'setToken',
|
'setToken',
|
||||||
'clearToken'
|
'clearToken',
|
||||||
|
'emojiUsed',
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultStorage = (() => {
|
const defaultStorage = (() => {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import announcementsModule from './modules/announcements.js'
|
||||||
import editStatusModule from './modules/editStatus.js'
|
import editStatusModule from './modules/editStatus.js'
|
||||||
import statusHistoryModule from './modules/statusHistory.js'
|
import statusHistoryModule from './modules/statusHistory.js'
|
||||||
import tagModule from './modules/tags.js'
|
import tagModule from './modules/tags.js'
|
||||||
|
import recentEmojisModule from './modules/recentEmojis.js'
|
||||||
|
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
@ -47,7 +48,8 @@ const persistedStateOptions = {
|
||||||
paths: [
|
paths: [
|
||||||
'config',
|
'config',
|
||||||
'users.lastLoginName',
|
'users.lastLoginName',
|
||||||
'oauth'
|
'oauth',
|
||||||
|
'recentEmojis.emojis',
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,7 +100,8 @@ const persistedStateOptions = {
|
||||||
announcements: announcementsModule,
|
announcements: announcementsModule,
|
||||||
editStatus: editStatusModule,
|
editStatus: editStatusModule,
|
||||||
statusHistory: statusHistoryModule,
|
statusHistory: statusHistoryModule,
|
||||||
tags: tagModule
|
tags: tagModule,
|
||||||
|
recentEmojis: recentEmojisModule,
|
||||||
},
|
},
|
||||||
plugins,
|
plugins,
|
||||||
strict: false // Socket modifies itself, let's ignore this for now.
|
strict: false // Socket modifies itself, let's ignore this for now.
|
||||||
|
|
|
@ -1,18 +1,29 @@
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { WSConnectionStatus } from '../services/api/api.service.js'
|
import { WSConnectionStatus } from '../services/api/api.service.js'
|
||||||
|
import { map } from 'lodash'
|
||||||
|
|
||||||
const retryTimeout = (multiplier) => 1000 * multiplier
|
const retryTimeout = (multiplier) => 1000 * multiplier
|
||||||
|
|
||||||
const isVisible = (store, message, visibility) => {
|
const isVisible = (store, message, visibility) => {
|
||||||
if (visibility === 'all') {
|
if (visibility == 'all') {
|
||||||
return true
|
return true
|
||||||
} else if (visibility === 'following') {
|
}
|
||||||
|
|
||||||
|
if (visibility == 'following') {
|
||||||
|
if (message.in_reply_to_user_id === null) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
return store.getters.relationship(message.in_reply_to_user_id).following
|
return store.getters.relationship(message.in_reply_to_user_id).following
|
||||||
} else if (visibility === 'self') {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibility == 'self') {
|
||||||
return message.in_reply_to_user_id === store.rootState.users.currentUser.id
|
return message.in_reply_to_user_id === store.rootState.users.currentUser.id
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
state: {
|
state: {
|
||||||
retryMultiplier: 1,
|
retryMultiplier: 1,
|
||||||
|
@ -40,9 +51,6 @@ const api = {
|
||||||
setSocket (state, socket) {
|
setSocket (state, socket) {
|
||||||
state.socket = socket
|
state.socket = socket
|
||||||
},
|
},
|
||||||
setFollowRequests (state, value) {
|
|
||||||
state.followRequests = value
|
|
||||||
},
|
|
||||||
setMastoUserSocketStatus (state, value) {
|
setMastoUserSocketStatus (state, value) {
|
||||||
state.mastoUserSocketStatus = value
|
state.mastoUserSocketStatus = value
|
||||||
},
|
},
|
||||||
|
@ -51,6 +59,15 @@ const api = {
|
||||||
},
|
},
|
||||||
resetRetryMultiplier (state) {
|
resetRetryMultiplier (state) {
|
||||||
state.retryMultiplier = 1
|
state.retryMultiplier = 1
|
||||||
|
},
|
||||||
|
setFollowRequests (state, value) {
|
||||||
|
state.followRequests = [...value]
|
||||||
|
},
|
||||||
|
saveFollowRequests (state, requests) {
|
||||||
|
state.followRequests = [...state.followRequests, ...requests]
|
||||||
|
},
|
||||||
|
saveFollowRequestPagination (state, pagination) {
|
||||||
|
state.followRequestsPagination = pagination
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -240,24 +257,22 @@ const api = {
|
||||||
...rest
|
...rest
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// Follow requests
|
|
||||||
startFetchingFollowRequests (store) {
|
|
||||||
if (store.state.fetchers['followRequests']) return
|
|
||||||
const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
|
|
||||||
|
|
||||||
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
|
|
||||||
},
|
|
||||||
stopFetchingFollowRequests (store) {
|
|
||||||
const fetcher = store.state.fetchers.followRequests
|
|
||||||
if (!fetcher) return
|
|
||||||
store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
|
|
||||||
},
|
|
||||||
removeFollowRequest (store, request) {
|
removeFollowRequest (store, request) {
|
||||||
let requests = store.state.followRequests.filter((it) => it !== request)
|
let requests = [...store.state.followRequests].filter((it) => it.id !== request.id)
|
||||||
store.commit('setFollowRequests', requests)
|
store.commit('setFollowRequests', requests)
|
||||||
},
|
},
|
||||||
|
fetchFollowRequests ({ rootState, commit }) {
|
||||||
|
const pagination = rootState.api.followRequestsPagination
|
||||||
|
return rootState.api.backendInteractor.getFollowRequests({ pagination })
|
||||||
|
.then((requests) => {
|
||||||
|
if (requests.data.length > 0) {
|
||||||
|
commit('addNewUsers', requests.data)
|
||||||
|
commit('saveFollowRequests', requests.data)
|
||||||
|
commit('saveFollowRequestPagination', requests.pagination)
|
||||||
|
}
|
||||||
|
return requests
|
||||||
|
})
|
||||||
|
},
|
||||||
// Lists
|
// Lists
|
||||||
startFetchingLists (store) {
|
startFetchingLists (store) {
|
||||||
if (store.state.fetchers['lists']) return
|
if (store.state.fetchers['lists']) return
|
||||||
|
|
50
src/modules/recentEmojis.js
Normal file
50
src/modules/recentEmojis.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// each row is 7 emojis, 6 rows chosen arbitrarily. i don't think more than
|
||||||
|
// that are going to be useful.
|
||||||
|
const RECENT_MAX = 7 * 6
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
emojis: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const recentEmojis = {
|
||||||
|
state: defaultState,
|
||||||
|
|
||||||
|
mutations: {
|
||||||
|
emojiUsed ({ emojis }, emoji) {
|
||||||
|
if (emoji.displayText === undefined || emoji.displayText === null) {
|
||||||
|
console.error('emojiUsed was called with a bad emoji object: ', emoji)
|
||||||
|
return
|
||||||
|
} else if (emoji.displayText.includes('@')) {
|
||||||
|
console.error('emojiUsed was called with a remote emoji: ', emoji)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = emojis.indexOf(emoji.displayText)
|
||||||
|
|
||||||
|
if (i === -1) {
|
||||||
|
// not in `emojis` yet, insert and truncate if necessary
|
||||||
|
const newLength = emojis.unshift(emoji.displayText)
|
||||||
|
if (newLength > RECENT_MAX) {
|
||||||
|
emojis.pop()
|
||||||
|
}
|
||||||
|
} else if (i !== 0) {
|
||||||
|
// emoji is already in `emojis` but needs to be bumped to the top
|
||||||
|
emojis.splice(i, 1)
|
||||||
|
emojis.unshift(emoji.displayText)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => {
|
||||||
|
const allEmojis = rootState.instance.emoji.concat(rootState.instance.customEmoji)
|
||||||
|
let emojiObject = allEmojis.find(emoji => emoji.displayText === displayText)
|
||||||
|
if (emojiObject !== undefined) {
|
||||||
|
objects.push(emojiObject)
|
||||||
|
}
|
||||||
|
return objects
|
||||||
|
}, []),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default recentEmojis
|
|
@ -265,6 +265,12 @@ export const mutations = {
|
||||||
signUpFailure (state, errors) {
|
signUpFailure (state, errors) {
|
||||||
state.signUpPending = false
|
state.signUpPending = false
|
||||||
state.signUpErrors = errors
|
state.signUpErrors = errors
|
||||||
|
},
|
||||||
|
decrementFollowRequestsCount (store) {
|
||||||
|
store.currentUser.follow_requests_count--
|
||||||
|
},
|
||||||
|
incrementFollowRequestsCount (store) {
|
||||||
|
store.currentUser.follow_requests_count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,6 +510,12 @@ const users = {
|
||||||
store.commit('setUserForNotification', notification)
|
store.commit('setUserForNotification', notification)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
decrementFollowRequestsCount (store) {
|
||||||
|
store.commit('decrementFollowRequestsCount')
|
||||||
|
},
|
||||||
|
incrementFollowRequestsCount (store) {
|
||||||
|
store.commit('incrementFollowRequestsCount')
|
||||||
|
},
|
||||||
searchUsers ({ rootState, commit }, { query }) {
|
searchUsers ({ rootState, commit }, { query }) {
|
||||||
return rootState.api.backendInteractor.searchUsers({ query })
|
return rootState.api.backendInteractor.searchUsers({ query })
|
||||||
.then((users) => {
|
.then((users) => {
|
||||||
|
@ -567,7 +579,6 @@ const users = {
|
||||||
store.dispatch('stopFetchingTimeline', 'friends')
|
store.dispatch('stopFetchingTimeline', 'friends')
|
||||||
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
|
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
|
||||||
store.dispatch('stopFetchingNotifications')
|
store.dispatch('stopFetchingNotifications')
|
||||||
store.dispatch('stopFetchingFollowRequests')
|
|
||||||
store.dispatch('stopFetchingConfig')
|
store.dispatch('stopFetchingConfig')
|
||||||
store.commit('clearNotifications')
|
store.commit('clearNotifications')
|
||||||
store.commit('resetStatuses')
|
store.commit('resetStatuses')
|
||||||
|
@ -626,13 +637,14 @@ const users = {
|
||||||
|
|
||||||
// Get user mutes
|
// Get user mutes
|
||||||
store.dispatch('fetchMutes')
|
store.dispatch('fetchMutes')
|
||||||
|
|
||||||
store.dispatch('setLayoutWidth', windowWidth())
|
store.dispatch('setLayoutWidth', windowWidth())
|
||||||
store.dispatch('setLayoutHeight', windowHeight())
|
store.dispatch('setLayoutHeight', windowHeight())
|
||||||
store.dispatch('getSupportedTranslationlanguages')
|
store.dispatch('getSupportedTranslationlanguages')
|
||||||
store.dispatch('getSettingsProfile')
|
store.dispatch('getSettingsProfile')
|
||||||
store.dispatch('listSettingsProfiles')
|
store.dispatch('listSettingsProfiles')
|
||||||
store.dispatch('startFetchingConfig')
|
store.dispatch('startFetchingConfig')
|
||||||
|
store.dispatch('startFetchingAnnouncements')
|
||||||
|
store.dispatch('startFetchingReports')
|
||||||
|
|
||||||
// Fetch our friends
|
// Fetch our friends
|
||||||
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
|
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
|
||||||
|
|
|
@ -406,14 +406,6 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => {
|
||||||
.then((data) => data.json())
|
.then((data) => data.json())
|
||||||
.then((data) => data.map(parseUser))
|
.then((data) => data.map(parseUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFollowRequests = ({ credentials }) => {
|
|
||||||
const url = MASTODON_FOLLOW_REQUESTS_URL
|
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
|
||||||
.then((data) => data.json())
|
|
||||||
.then((data) => data.map(parseUser))
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchLists = ({ credentials }) => {
|
const fetchLists = ({ credentials }) => {
|
||||||
const url = MASTODON_LISTS_URL
|
const url = MASTODON_LISTS_URL
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return fetch(url, { headers: authHeaders(credentials) })
|
||||||
|
@ -1601,6 +1593,26 @@ const getFollowedHashtags = ({ credentials, pagination: savedPagination }) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getFollowRequests = ({ credentials, pagination: savedPagination }) => {
|
||||||
|
const queryParams = new URLSearchParams()
|
||||||
|
if (savedPagination?.maxId) {
|
||||||
|
queryParams.append('max_id', savedPagination.maxId)
|
||||||
|
}
|
||||||
|
const url = `${MASTODON_FOLLOW_REQUESTS_URL}?${queryParams.toString()}`
|
||||||
|
let pagination = {};
|
||||||
|
return fetch(url, {
|
||||||
|
credentials
|
||||||
|
}).then((data) => {
|
||||||
|
pagination = parseLinkHeaderPagination(data.headers.get('Link'), { flakeId: true });
|
||||||
|
return data.json()
|
||||||
|
}).then((data) => {
|
||||||
|
return {
|
||||||
|
pagination,
|
||||||
|
data: data.map(parseUser)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
|
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
|
||||||
return Object.entries({
|
return Object.entries({
|
||||||
...(credentials
|
...(credentials
|
||||||
|
@ -1790,7 +1802,6 @@ const apiService = {
|
||||||
mfaConfirmOTP,
|
mfaConfirmOTP,
|
||||||
addBackup,
|
addBackup,
|
||||||
listBackups,
|
listBackups,
|
||||||
fetchFollowRequests,
|
|
||||||
fetchLists,
|
fetchLists,
|
||||||
createList,
|
createList,
|
||||||
getList,
|
getList,
|
||||||
|
@ -1841,6 +1852,7 @@ const apiService = {
|
||||||
followHashtag,
|
followHashtag,
|
||||||
unfollowHashtag,
|
unfollowHashtag,
|
||||||
getFollowedHashtags,
|
getFollowedHashtags,
|
||||||
|
getFollowRequests
|
||||||
}
|
}
|
||||||
|
|
||||||
export default apiService
|
export default apiService
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js'
|
import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js'
|
||||||
import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js'
|
import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js'
|
||||||
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
|
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
|
||||||
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
|
|
||||||
import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js'
|
import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js'
|
||||||
import announcementsFetcher from '../../services/announcements_fetcher/announcements_fetcher.service.js'
|
import announcementsFetcher from '../../services/announcements_fetcher/announcements_fetcher.service.js'
|
||||||
import configFetcher from '../config_fetcher/config_fetcher.service.js'
|
import configFetcher from '../config_fetcher/config_fetcher.service.js'
|
||||||
|
@ -28,10 +27,6 @@ const backendInteractorService = credentials => ({
|
||||||
return notificationsFetcher.fetchAndUpdate({ ...args, credentials })
|
return notificationsFetcher.fetchAndUpdate({ ...args, credentials })
|
||||||
},
|
},
|
||||||
|
|
||||||
startFetchingFollowRequests ({ store }) {
|
|
||||||
return followRequestFetcher.startFetching({ store, credentials })
|
|
||||||
},
|
|
||||||
|
|
||||||
startFetchingLists ({ store }) {
|
startFetchingLists ({ store }) {
|
||||||
return listsFetcher.startFetching({ store, credentials })
|
return listsFetcher.startFetching({ store, credentials })
|
||||||
},
|
},
|
||||||
|
|
|
@ -90,6 +90,7 @@ export const parseUser = (data) => {
|
||||||
output.friends_count = data.following_count
|
output.friends_count = data.following_count
|
||||||
|
|
||||||
output.bot = data.bot
|
output.bot = data.bot
|
||||||
|
output.follow_requests_count = data.follow_requests_count
|
||||||
if (data.akkoma) {
|
if (data.akkoma) {
|
||||||
output.instance = data.akkoma.instance
|
output.instance = data.akkoma.instance
|
||||||
output.status_ttl_days = data.akkoma.status_ttl_days
|
output.status_ttl_days = data.akkoma.status_ttl_days
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import apiService from '../api/api.service.js'
|
|
||||||
import { promiseInterval } from '../promise_interval/promise_interval.js'
|
|
||||||
|
|
||||||
const fetchAndUpdate = ({ store, credentials }) => {
|
|
||||||
return apiService.fetchFollowRequests({ credentials })
|
|
||||||
.then((requests) => {
|
|
||||||
store.commit('setFollowRequests', requests)
|
|
||||||
store.commit('addNewUsers', requests)
|
|
||||||
}, () => {})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
const startFetching = ({ credentials, store }) => {
|
|
||||||
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
|
|
||||||
boundFetchAndUpdate()
|
|
||||||
return promiseInterval(boundFetchAndUpdate, 240000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const followRequestFetcher = {
|
|
||||||
startFetching
|
|
||||||
}
|
|
||||||
|
|
||||||
export default followRequestFetcher
|
|
Loading…
Reference in a new issue