Merge branch 'emoji-optimizations' into 'develop'
Emoji fixes, optimizations and improvements Closes #690, #686, #682, #674, and #678 See merge request pleroma/pleroma-fe!969
This commit is contained in:
commit
2b68134ab0
16 changed files with 250 additions and 94 deletions
12
src/App.scss
12
src/App.scss
|
@ -661,6 +661,18 @@ nav {
|
|||
color: var(--alertErrorPanelText, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: $fallback--alertWarning;
|
||||
background-color: var(--alertWarning, $fallback--alertWarning);
|
||||
color: $fallback--text;
|
||||
color: var(--alertWarningText, $fallback--text);
|
||||
|
||||
.panel-heading & {
|
||||
color: $fallback--text;
|
||||
color: var(--alertWarningPanelText, $fallback--text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.faint {
|
||||
|
|
|
@ -17,6 +17,7 @@ $fallback--cGreen: #0fa00f;
|
|||
$fallback--cOrange: orange;
|
||||
|
||||
$fallback--alertError: rgba(211,16,20,.5);
|
||||
$fallback--alertWarning: rgba(111,111,20,.5);
|
||||
|
||||
$fallback--panelRadius: 10px;
|
||||
$fallback--checkboxRadius: 2px;
|
||||
|
|
|
@ -173,58 +173,6 @@ const getStickers = async ({ store }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getStaticEmoji = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/static/emoji.json')
|
||||
if (res.ok) {
|
||||
const values = await res.json()
|
||||
const emoji = Object.keys(values).map((key) => {
|
||||
return {
|
||||
displayText: key,
|
||||
imageUrl: false,
|
||||
replacement: values[key]
|
||||
}
|
||||
}).sort((a, b) => a.displayText - b.displayText)
|
||||
store.dispatch('setInstanceOption', { name: 'emoji', value: emoji })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Can't load static emoji")
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
// This is also used to indicate if we have a 'pleroma backend' or not.
|
||||
// Somewhat weird, should probably be somewhere else.
|
||||
const getCustomEmoji = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/api/pleroma/emoji.json')
|
||||
if (res.ok) {
|
||||
const result = await res.json()
|
||||
const values = Array.isArray(result) ? Object.assign({}, ...result) : result
|
||||
const emoji = Object.entries(values).map(([key, value]) => {
|
||||
const imageUrl = value.image_url
|
||||
return {
|
||||
displayText: key,
|
||||
imageUrl: imageUrl ? store.state.instance.server + imageUrl : value,
|
||||
tags: imageUrl ? value.tags.sort((a, b) => a > b ? 1 : 0) : ['utf'],
|
||||
replacement: `:${key}: `
|
||||
}
|
||||
// Technically could use tags but those are kinda useless right now, should have been "pack" field, that would be more useful
|
||||
}).sort((a, b) => a.displayText.toLowerCase() > b.displayText.toLowerCase() ? 1 : 0)
|
||||
store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji })
|
||||
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false })
|
||||
console.warn("Can't load custom emojis, maybe not a Pleroma instance?")
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
const getAppSecret = async ({ store }) => {
|
||||
const { state, commit } = store
|
||||
const { oauth, instance } = state
|
||||
|
@ -259,6 +207,7 @@ const getNodeInfo = async ({ store }) => {
|
|||
|
||||
const software = data.software
|
||||
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
|
||||
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
|
||||
|
||||
const frontendVersion = window.___pleromafe_commit_hash
|
||||
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
|
||||
|
@ -315,8 +264,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
getTOS({ store }),
|
||||
getInstancePanel({ store }),
|
||||
getStickers({ store }),
|
||||
getStaticEmoji({ store }),
|
||||
getCustomEmoji({ store }),
|
||||
getNodeInfo({ store })
|
||||
])
|
||||
|
||||
|
|
|
@ -165,6 +165,7 @@ const EmojiInput = {
|
|||
methods: {
|
||||
triggerShowPicker () {
|
||||
this.showPicker = true
|
||||
this.$refs.picker.startEmojiLoad()
|
||||
this.$nextTick(() => {
|
||||
this.scrollIntoView()
|
||||
})
|
||||
|
@ -181,6 +182,7 @@ const EmojiInput = {
|
|||
this.showPicker = !this.showPicker
|
||||
if (this.showPicker) {
|
||||
this.scrollIntoView()
|
||||
this.$refs.picker.startEmojiLoad()
|
||||
}
|
||||
},
|
||||
replace (replacement) {
|
||||
|
@ -306,6 +308,16 @@ const EmojiInput = {
|
|||
} else {
|
||||
scrollerRef.scrollTop = targetScroll
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const { offsetHeight } = this.input.elm
|
||||
const { picker } = this.$refs
|
||||
const pickerBottom = picker.$el.getBoundingClientRect().bottom
|
||||
if (pickerBottom > window.innerHeight) {
|
||||
picker.$el.style.top = 'auto'
|
||||
picker.$el.style.bottom = offsetHeight + 'px'
|
||||
}
|
||||
})
|
||||
},
|
||||
onTransition (e) {
|
||||
this.resize()
|
||||
|
@ -419,11 +431,14 @@ const EmojiInput = {
|
|||
this.caret = selectionStart
|
||||
},
|
||||
resize () {
|
||||
const { panel } = this.$refs
|
||||
const { panel, picker } = this.$refs
|
||||
if (!panel) return
|
||||
const { offsetHeight, offsetTop } = this.input.elm
|
||||
this.$refs.panel.style.top = (offsetTop + offsetHeight) + 'px'
|
||||
this.$refs.picker.$el.style.top = (offsetTop + offsetHeight) + 'px'
|
||||
const offsetBottom = offsetTop + offsetHeight
|
||||
|
||||
panel.style.top = offsetBottom + 'px'
|
||||
picker.$el.style.top = offsetBottom + 'px'
|
||||
picker.$el.style.bottom = 'auto'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div
|
||||
v-click-outside="onClickOutside"
|
||||
class="emoji-input"
|
||||
:class="{ 'with-picker': !hideEmojiButton }"
|
||||
>
|
||||
<slot />
|
||||
<template v-if="enableEmojiPicker">
|
||||
|
@ -63,6 +64,10 @@
|
|||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
&.with-picker input {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.emoji-picker-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import Checkbox from '../checkbox/checkbox.vue'
|
||||
|
||||
// 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 filterByKeyword = (list, keyword = '') => {
|
||||
return list.filter(x => x.displayText.includes(keyword))
|
||||
}
|
||||
|
@ -18,7 +25,10 @@ const EmojiPicker = {
|
|||
activeGroup: 'custom',
|
||||
showingStickers: false,
|
||||
groupsScrolledClass: 'scrolled-top',
|
||||
keepOpen: false
|
||||
keepOpen: false,
|
||||
customEmojiBufferSlice: LOAD_EMOJI_BY,
|
||||
customEmojiTimeout: null,
|
||||
customEmojiLoadAllConfirmed: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -26,10 +36,22 @@ const EmojiPicker = {
|
|||
Checkbox
|
||||
},
|
||||
methods: {
|
||||
onStickerUploaded (e) {
|
||||
this.$emit('sticker-uploaded', e)
|
||||
},
|
||||
onStickerUploadFailed (e) {
|
||||
this.$emit('sticker-upload-failed', e)
|
||||
},
|
||||
onEmoji (emoji) {
|
||||
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
|
||||
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
||||
},
|
||||
onScroll (e) {
|
||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
||||
this.updateScrolledClass(target)
|
||||
this.scrolledGroup(target)
|
||||
this.triggerLoadMore(target)
|
||||
},
|
||||
highlight (key) {
|
||||
const ref = this.$refs['group-' + key]
|
||||
const top = ref[0].offsetTop
|
||||
|
@ -39,9 +61,7 @@ const EmojiPicker = {
|
|||
this.$refs['emoji-groups'].scrollTop = top + 1
|
||||
})
|
||||
},
|
||||
scrolledGroup (e) {
|
||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
||||
const top = target.scrollTop + 5
|
||||
updateScrolledClass (target) {
|
||||
if (target.scrollTop <= 5) {
|
||||
this.groupsScrolledClass = 'scrolled-top'
|
||||
} else if (target.scrollTop >= target.scrollTopMax - 5) {
|
||||
|
@ -49,6 +69,28 @@ const EmojiPicker = {
|
|||
} else {
|
||||
this.groupsScrolledClass = 'scrolled-middle'
|
||||
}
|
||||
},
|
||||
triggerLoadMore (target) {
|
||||
const ref = this.$refs['group-end-custom'][0]
|
||||
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]
|
||||
|
@ -58,22 +100,41 @@ const EmojiPicker = {
|
|||
})
|
||||
})
|
||||
},
|
||||
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 () {
|
||||
this.showingStickers = !this.showingStickers
|
||||
},
|
||||
setShowStickers (value) {
|
||||
this.showingStickers = value
|
||||
},
|
||||
onStickerUploaded (e) {
|
||||
this.$emit('sticker-uploaded', e)
|
||||
},
|
||||
onStickerUploadFailed (e) {
|
||||
this.$emit('sticker-upload-failed', e)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyword () {
|
||||
this.scrolledGroup()
|
||||
this.customEmojiLoadAllConfirmed = false
|
||||
this.onScroll()
|
||||
this.startEmojiLoad(true)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -86,15 +147,25 @@ const EmojiPicker = {
|
|||
}
|
||||
return 0
|
||||
},
|
||||
filteredEmoji () {
|
||||
return filterByKeyword(
|
||||
this.$store.state.instance.customEmoji || [],
|
||||
this.keyword
|
||||
)
|
||||
},
|
||||
customEmojiBuffer () {
|
||||
return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
|
||||
},
|
||||
emojis () {
|
||||
const standardEmojis = this.$store.state.instance.emoji || []
|
||||
const customEmojis = this.$store.state.instance.customEmoji || []
|
||||
const customEmojis = this.customEmojiBuffer
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'custom',
|
||||
text: this.$t('emoji.custom'),
|
||||
icon: 'icon-smile',
|
||||
emojis: filterByKeyword(customEmojis, this.keyword)
|
||||
emojis: customEmojis
|
||||
},
|
||||
{
|
||||
id: 'standard',
|
||||
|
|
|
@ -6,15 +6,25 @@
|
|||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 320px;
|
||||
margin: 0 !important;
|
||||
z-index: 1;
|
||||
|
||||
.keep-open {
|
||||
.keep-open,
|
||||
.too-many-emoji {
|
||||
padding: 7px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.too-many-emoji {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.keep-open-label {
|
||||
padding: 0 7px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
|
@ -24,7 +34,7 @@
|
|||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 0;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
|
@ -32,12 +42,16 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.emoji-groups {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.additional-tabs {
|
||||
border-left: 1px solid;
|
||||
border-left-color: $fallback--icon;
|
||||
border-left-color: var(--icon, $fallback--icon);
|
||||
padding-left: 7px;
|
||||
flex: 0 0 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.additional-tabs,
|
||||
|
@ -68,7 +82,7 @@
|
|||
}
|
||||
|
||||
.sticker-picker {
|
||||
flex: 1 1 0
|
||||
flex: 1 1 auto
|
||||
}
|
||||
|
||||
.stickers,
|
||||
|
@ -76,7 +90,7 @@
|
|||
&-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 0;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
|
||||
&.hidden {
|
||||
|
@ -90,7 +104,7 @@
|
|||
.emoji {
|
||||
&-search {
|
||||
padding: 5px;
|
||||
flex: 0 0 0;
|
||||
flex: 0 0 auto;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
ref="emoji-groups"
|
||||
class="emoji-groups"
|
||||
:class="groupsScrolledClass"
|
||||
@scroll="scrolledGroup"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div
|
||||
v-for="group in emojisView"
|
||||
|
@ -73,6 +73,7 @@
|
|||
:src="emoji.imageUrl"
|
||||
>
|
||||
</span>
|
||||
<span :ref="'group-end-' + group.id" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="keep-open">
|
||||
|
|
|
@ -276,11 +276,15 @@ const PostStatusForm = {
|
|||
return
|
||||
}
|
||||
|
||||
const rootRef = this.$refs['root']
|
||||
const formRef = this.$refs['form']
|
||||
const bottomRef = this.$refs['bottom']
|
||||
/* Scroller is either `window` (replies in TL), sidebar (main post form,
|
||||
* replies in notifs) or mobile post form. Note that getting and setting
|
||||
* scroll is different for `Window` and `Element`s
|
||||
*/
|
||||
const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom']
|
||||
const bottomBottomPadding = Number(bottomBottomPaddingStr.substring(0, bottomBottomPaddingStr.length - 2))
|
||||
|
||||
const scrollerRef = this.$el.closest('.sidebar-scroller') ||
|
||||
this.$el.closest('.post-form-modal-view') ||
|
||||
window
|
||||
|
@ -292,9 +296,6 @@ const PostStatusForm = {
|
|||
const bottomPadding = Number(bottomPaddingStr.substring(0, bottomPaddingStr.length - 2))
|
||||
const vertPadding = topPadding + bottomPadding
|
||||
|
||||
const oldHeightStr = target.style.height || ''
|
||||
const oldHeight = Number(oldHeightStr.substring(0, oldHeightStr.length - 2))
|
||||
|
||||
/* Explanation:
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
|
||||
|
@ -306,7 +307,7 @@ const PostStatusForm = {
|
|||
* SHRINK the textarea when there's extra space. To workaround that we set
|
||||
* height to 'auto' which makes textarea tiny again, so that scrollHeight
|
||||
* will match text height again. HOWEVER, shrinking textarea can screw with
|
||||
* the scroll since there might be not enough padding around root to even
|
||||
* the scroll since there might be not enough padding around form-bottom to even
|
||||
* warrant a scroll, so it will jump to 0 and refuse to move anywhere,
|
||||
* so we check current scroll position before shrinking and then restore it
|
||||
* with needed delta.
|
||||
|
@ -327,16 +328,21 @@ const PostStatusForm = {
|
|||
target.style.height = `${newHeight}px`
|
||||
// END content size update
|
||||
|
||||
// We check where the bottom border of root element is, this uses findOffset
|
||||
// We check where the bottom border of form-bottom element is, this uses findOffset
|
||||
// to find offset relative to scrollable container (scroller)
|
||||
const rootBottomBorder = rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top
|
||||
|
||||
const textareaSizeChangeDelta = newHeight - oldHeight || 0
|
||||
const isBottomObstructed = scrollerBottomBorder < rootBottomBorder
|
||||
const rootChangeDelta = rootBottomBorder - scrollerBottomBorder
|
||||
const totalDelta = textareaSizeChangeDelta +
|
||||
(isBottomObstructed ? rootChangeDelta : 0)
|
||||
const bottomBottomBorder = bottomRef.offsetHeight + findOffset(bottomRef, scrollerRef).top + bottomBottomPadding
|
||||
|
||||
const isBottomObstructed = scrollerBottomBorder < bottomBottomBorder
|
||||
const isFormBiggerThanScroller = scrollerHeight < formRef.offsetHeight
|
||||
const bottomChangeDelta = bottomBottomBorder - scrollerBottomBorder
|
||||
// The intention is basically this;
|
||||
// Keep form-bottom always visible so that submit button is in view EXCEPT
|
||||
// if form element bigger than scroller and caret isn't at the end, so that
|
||||
// if you scroll up and edit middle of text you won't get scrolled back to bottom
|
||||
const shouldScrollToBottom = isBottomObstructed &&
|
||||
!(isFormBiggerThanScroller &&
|
||||
this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length)
|
||||
const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0
|
||||
const targetScroll = currentScroll + totalDelta
|
||||
|
||||
if (scrollerRef === window) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
ref="root"
|
||||
ref="form"
|
||||
class="post-status-form"
|
||||
>
|
||||
<form
|
||||
|
@ -160,7 +160,10 @@
|
|||
:visible="pollFormVisible"
|
||||
@update-poll="setPoll"
|
||||
/>
|
||||
<div class="form-bottom">
|
||||
<div
|
||||
ref="bottom"
|
||||
class="form-bottom"
|
||||
>
|
||||
<div class="form-bottom-left">
|
||||
<media-upload
|
||||
ref="mediaUpload"
|
||||
|
|
|
@ -74,6 +74,7 @@ export default {
|
|||
topBarLinkColorLocal: undefined,
|
||||
|
||||
alertErrorColorLocal: undefined,
|
||||
alertWarningColorLocal: undefined,
|
||||
|
||||
badgeOpacityLocal: undefined,
|
||||
badgeNotificationColorLocal: undefined,
|
||||
|
@ -147,6 +148,7 @@ export default {
|
|||
btnText: this.btnTextColorLocal,
|
||||
|
||||
alertError: this.alertErrorColorLocal,
|
||||
alertWarning: this.alertWarningColorLocal,
|
||||
badgeNotification: this.badgeNotificationColorLocal,
|
||||
|
||||
faint: this.faintColorLocal,
|
||||
|
@ -230,6 +232,7 @@ export default {
|
|||
topBar: hex2rgb(colors.topBar),
|
||||
input: hex2rgb(colors.input),
|
||||
alertError: hex2rgb(colors.alertError),
|
||||
alertWarning: hex2rgb(colors.alertWarning),
|
||||
badgeNotification: hex2rgb(colors.badgeNotification)
|
||||
}
|
||||
|
||||
|
|
|
@ -201,6 +201,13 @@
|
|||
:fallback="previewTheme.colors.alertError"
|
||||
/>
|
||||
<ContrastRatio :contrast="previewContrast.alertError" />
|
||||
<ColorInput
|
||||
v-model="alertWarningColorLocal"
|
||||
name="alertWarning"
|
||||
:label="$t('settings.style.advanced_colors.alert_warning')"
|
||||
:fallback="previewTheme.colors.alertWarning"
|
||||
/>
|
||||
<ContrastRatio :contrast="previewContrast.alertWarning" />
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
|
||||
|
|
|
@ -114,7 +114,9 @@
|
|||
"search_emoji": "Search for an emoji",
|
||||
"add_emoji": "Insert emoji",
|
||||
"custom": "Custom emoji",
|
||||
"unicode": "Unicode emoji"
|
||||
"unicode": "Unicode emoji",
|
||||
"load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
|
||||
"load_all": "Loading all {emojiAmount} emoji"
|
||||
},
|
||||
"interactions": {
|
||||
"favs_repeats": "Repeats and Favorites",
|
||||
|
@ -391,6 +393,7 @@
|
|||
"_tab_label": "Advanced",
|
||||
"alert": "Alert background",
|
||||
"alert_error": "Error",
|
||||
"alert_warning": "Warning",
|
||||
"badge": "Badge background",
|
||||
"badge_notification": "Notification",
|
||||
"panel_header": "Panel header",
|
||||
|
|
|
@ -36,7 +36,9 @@ const defaultState = {
|
|||
// Nasty stuff
|
||||
pleromaBackend: true,
|
||||
emoji: [],
|
||||
emojiFetched: false,
|
||||
customEmoji: [],
|
||||
customEmojiFetched: false,
|
||||
restrictedNicknames: [],
|
||||
postFormats: [],
|
||||
|
||||
|
@ -94,9 +96,68 @@ const instance = {
|
|||
break
|
||||
}
|
||||
},
|
||||
async getStaticEmoji ({ commit }) {
|
||||
try {
|
||||
const res = await window.fetch('/static/emoji.json')
|
||||
if (res.ok) {
|
||||
const values = await res.json()
|
||||
const emoji = Object.keys(values).map((key) => {
|
||||
return {
|
||||
displayText: key,
|
||||
imageUrl: false,
|
||||
replacement: values[key]
|
||||
}
|
||||
}).sort((a, b) => a.displayText - b.displayText)
|
||||
commit('setInstanceOption', { name: 'emoji', value: emoji })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Can't load static emoji")
|
||||
console.warn(e)
|
||||
}
|
||||
},
|
||||
|
||||
async getCustomEmoji ({ commit, state }) {
|
||||
try {
|
||||
const res = await window.fetch('/api/pleroma/emoji.json')
|
||||
if (res.ok) {
|
||||
const result = await res.json()
|
||||
const values = Array.isArray(result) ? Object.assign({}, ...result) : result
|
||||
const emoji = Object.entries(values).map(([key, value]) => {
|
||||
const imageUrl = value.image_url
|
||||
return {
|
||||
displayText: key,
|
||||
imageUrl: imageUrl ? state.server + imageUrl : value,
|
||||
tags: imageUrl ? value.tags.sort((a, b) => a > b ? 1 : 0) : ['utf'],
|
||||
replacement: `:${key}: `
|
||||
}
|
||||
// Technically could use tags but those are kinda useless right now,
|
||||
// should have been "pack" field, that would be more useful
|
||||
}).sort((a, b) => a.displayText.toLowerCase() > b.displayText.toLowerCase() ? 1 : 0)
|
||||
commit('setInstanceOption', { name: 'customEmoji', value: emoji })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Can't load custom emojis")
|
||||
console.warn(e)
|
||||
}
|
||||
},
|
||||
|
||||
setTheme ({ commit }, themeName) {
|
||||
commit('setInstanceOption', { name: 'theme', value: themeName })
|
||||
return setPreset(themeName, commit)
|
||||
},
|
||||
fetchEmoji ({ dispatch, state }) {
|
||||
if (!state.customEmojiFetched) {
|
||||
state.customEmojiFetched = true
|
||||
dispatch('getCustomEmoji')
|
||||
}
|
||||
if (!state.emojiFetched) {
|
||||
state.emojiFetched = true
|
||||
dispatch('getStaticEmoji')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -453,6 +453,8 @@ const users = {
|
|||
commit('setCurrentUser', user)
|
||||
commit('addNewUsers', [user])
|
||||
|
||||
store.dispatch('fetchEmoji')
|
||||
|
||||
getNotificationPermission()
|
||||
.then(permission => commit('setNotificationPermission', permission))
|
||||
|
||||
|
|
|
@ -215,6 +215,10 @@ const generateColors = (input) => {
|
|||
colors.alertErrorText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.bg), colors.text)
|
||||
colors.alertErrorPanelText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.panel), colors.panelText)
|
||||
|
||||
colors.alertWarning = col.alertWarning || Object.assign({}, colors.cOrange)
|
||||
colors.alertWarningText = getTextColor(alphaBlend(colors.alertWarning, opacity.alert, colors.bg), colors.text)
|
||||
colors.alertWarningPanelText = getTextColor(alphaBlend(colors.alertWarning, opacity.alert, colors.panel), colors.panelText)
|
||||
|
||||
colors.badgeNotification = col.badgeNotification || Object.assign({}, colors.cRed)
|
||||
colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
|
||||
|
||||
|
@ -222,6 +226,7 @@ const generateColors = (input) => {
|
|||
if (typeof v === 'undefined') return
|
||||
if (k === 'alert') {
|
||||
colors.alertError.a = v
|
||||
colors.alertWarning.a = v
|
||||
return
|
||||
}
|
||||
if (k === 'faint') {
|
||||
|
|
Loading…
Reference in a new issue