forked from cybrespace/pinafore
add custom emoji modal
This commit is contained in:
parent
b6eb997893
commit
18dab36e52
|
@ -5,6 +5,7 @@ import { switchToTheme } from '../_utils/themeEngine'
|
||||||
import { database } from '../_database/database'
|
import { database } from '../_database/database'
|
||||||
import { store } from '../_store/store'
|
import { store } from '../_store/store'
|
||||||
import { updateVerifyCredentialsForInstance } from './instances'
|
import { updateVerifyCredentialsForInstance } from './instances'
|
||||||
|
import { updateCustomEmojiForInstance } from './emoji'
|
||||||
|
|
||||||
const REDIRECT_URI = (typeof location !== 'undefined'
|
const REDIRECT_URI = (typeof location !== 'undefined'
|
||||||
? location.origin : 'https://pinafore.social') + '/settings/instances/add'
|
? location.origin : 'https://pinafore.social') + '/settings/instances/add'
|
||||||
|
@ -85,8 +86,9 @@ async function registerNewInstance (code) {
|
||||||
})
|
})
|
||||||
store.save()
|
store.save()
|
||||||
switchToTheme('default')
|
switchToTheme('default')
|
||||||
// fire off request for account so it's cached
|
// fire off these requests so they're cached
|
||||||
updateVerifyCredentialsForInstance(currentRegisteredInstanceName)
|
/* no await */ updateVerifyCredentialsForInstance(currentRegisteredInstanceName)
|
||||||
|
/* no await */ updateCustomEmojiForInstance(currentRegisteredInstanceName)
|
||||||
goto('/')
|
goto('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { cacheFirstUpdateAfter } from '../_utils/sync'
|
||||||
|
import { database } from '../_database/database'
|
||||||
|
import { getCustomEmoji } from '../_api/emoji'
|
||||||
|
import { store } from '../_store/store'
|
||||||
|
|
||||||
|
export async function updateCustomEmojiForInstance (instanceName) {
|
||||||
|
await cacheFirstUpdateAfter(
|
||||||
|
() => getCustomEmoji(instanceName),
|
||||||
|
() => database.getCustomEmoji(instanceName),
|
||||||
|
emoji => database.setCustomEmoji(instanceName, emoji),
|
||||||
|
emoji => {
|
||||||
|
let customEmoji = store.get('customEmoji')
|
||||||
|
customEmoji[instanceName] = emoji
|
||||||
|
store.set({customEmoji: customEmoji})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function insertEmoji (emoji) {
|
||||||
|
store.set({emojiToInsert: emoji})
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { basename } from './utils'
|
||||||
|
import { getWithTimeout } from '../_utils/ajax'
|
||||||
|
|
||||||
|
export async function getCustomEmoji (instanceName) {
|
||||||
|
let url = `${basename(instanceName)}/api/v1/custom_emojis`
|
||||||
|
return getWithTimeout(url)
|
||||||
|
}
|
|
@ -1,12 +1,10 @@
|
||||||
{{#if pressable}}
|
{{#if delegateKey}}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
aria-label="{{label}}"
|
aria-label="{{label}}"
|
||||||
aria-pressed="{{!!pressed}}"
|
aria-pressed="{{pressable ? !!pressed : ''}}"
|
||||||
class="{{computedClass}}"
|
class="{{computedClass}}"
|
||||||
disabled="{{disabled}}"
|
:disabled
|
||||||
delegate-key="{{delegateKey}}"
|
delegate-key="{{delegateKey}}" >
|
||||||
on:click
|
|
||||||
>
|
|
||||||
<svg>
|
<svg>
|
||||||
<use xlink:href="{{href}}" />
|
<use xlink:href="{{href}}" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -14,11 +12,10 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
aria-label="{{label}}"
|
aria-label="{{label}}"
|
||||||
|
aria-pressed="{{pressable ? !!pressed : ''}}"
|
||||||
class="{{computedClass}}"
|
class="{{computedClass}}"
|
||||||
disabled="{{disabled}}"
|
:disabled
|
||||||
delegate-key="{{delegateKey}}"
|
on:click >
|
||||||
on:click
|
|
||||||
>
|
|
||||||
<svg>
|
<svg>
|
||||||
<use xlink:href="{{href}}" />
|
<use xlink:href="{{href}}" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
const saveText = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
|
const saveText = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
|
||||||
|
|
||||||
this.observe('rawComposeText', rawComposeText => {
|
this.observe('rawComposeText', rawComposeText => {
|
||||||
let composeText = this.store.get('composeText')
|
let composeText = this.store.get('composeText')
|
||||||
let currentInstance = this.store.get('currentInstance')
|
let currentInstance = this.store.get('currentInstance')
|
||||||
|
@ -47,6 +48,23 @@
|
||||||
this.store.set({composeText: composeText})
|
this.store.set({composeText: composeText})
|
||||||
saveText()
|
saveText()
|
||||||
}, {init: false})
|
}, {init: false})
|
||||||
|
|
||||||
|
this.observe('emojiToInsert', emojiToInsert => {
|
||||||
|
if (!emojiToInsert) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
let idx = this.refs.textarea.selectionStart || 0
|
||||||
|
let oldText = this.store.get('rawComposeText')
|
||||||
|
let newText = oldText.substring(0, idx) +
|
||||||
|
':' + emojiToInsert.shortcode + ': ' +
|
||||||
|
oldText.substring(idx)
|
||||||
|
this.store.set({
|
||||||
|
rawComposeText: newText,
|
||||||
|
emojiToInsert: null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, {init: false})
|
||||||
},
|
},
|
||||||
ondestroy() {
|
ondestroy() {
|
||||||
mark('autosize.destroy()')
|
mark('autosize.destroy()')
|
||||||
|
@ -57,6 +75,7 @@
|
||||||
computed: {
|
computed: {
|
||||||
rawComposeText: ($rawComposeText) => $rawComposeText,
|
rawComposeText: ($rawComposeText) => $rawComposeText,
|
||||||
currentComposeText: ($currentComposeText) => $currentComposeText,
|
currentComposeText: ($currentComposeText) => $currentComposeText,
|
||||||
|
emojiToInsert: ($emojiToInsert) => $emojiToInsert
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -1,5 +1,9 @@
|
||||||
<div class="compose-box-toolbar">
|
<div class="compose-box-toolbar">
|
||||||
<IconButton label="Insert emoji" href="#fa-smile" />
|
<IconButton
|
||||||
|
label="Insert emoji"
|
||||||
|
href="#fa-smile"
|
||||||
|
on:click="onEmojiClick()"
|
||||||
|
/>
|
||||||
<IconButton label="Add media" href="#fa-camera" />
|
<IconButton label="Add media" href="#fa-camera" />
|
||||||
<IconButton label="Adjust privacy" href="#fa-globe" />
|
<IconButton label="Adjust privacy" href="#fa-globe" />
|
||||||
<IconButton label="Add content warning" href="#fa-exclamation-triangle" />
|
<IconButton label="Add content warning" href="#fa-exclamation-triangle" />
|
||||||
|
@ -14,9 +18,21 @@
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import IconButton from '../IconButton.html'
|
import IconButton from '../IconButton.html'
|
||||||
|
import { store } from '../../_store/store'
|
||||||
|
import { updateCustomEmojiForInstance } from '../../_actions/emoji'
|
||||||
|
import { importDialogs } from '../../_utils/asyncModules'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
IconButton
|
IconButton
|
||||||
|
},
|
||||||
|
store: () => store,
|
||||||
|
methods: {
|
||||||
|
async onEmojiClick() {
|
||||||
|
/* no await */ updateCustomEmojiForInstance(this.store.get('currentInstance'))
|
||||||
|
let dialogs = await importDialogs()
|
||||||
|
dialogs.showEmojiDialog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -0,0 +1,82 @@
|
||||||
|
<ModalDialog :label :shown :closed :title background="var(--main-bg)">
|
||||||
|
<div class="custom-emoji-container">
|
||||||
|
{{#if emojis.length}}
|
||||||
|
<ul class="custom-emoji-list">
|
||||||
|
{{#each emojis as emoji}}
|
||||||
|
<li class="custom-emoji">
|
||||||
|
<button type="button" on:click="onClickEmoji(emoji)">
|
||||||
|
<img src="{{$autoplayGifs ? emoji.url : emoji.static_url}}"
|
||||||
|
alt=":{{emoji.shortcode}}:"
|
||||||
|
title=":{{emoji.shortcode}}:"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<div class="custom-emoji-no-emoji">No custom emoji found for this instance.</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</ModalDialog>
|
||||||
|
<style>
|
||||||
|
.custom-emoji-container {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 400px;
|
||||||
|
height: 300px;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
.custom-emoji-no-emoji {
|
||||||
|
font-size: 1.3em;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.custom-emoji-list {
|
||||||
|
list-style: none;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(48px, 1fr));
|
||||||
|
grid-gap: 5px;
|
||||||
|
padding: 20px 10px;
|
||||||
|
}
|
||||||
|
.custom-emoji button {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: none;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.custom-emoji img {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import ModalDialog from './ModalDialog.html'
|
||||||
|
import { store } from '../../_store/store'
|
||||||
|
import { insertEmoji } from '../../_actions/emoji'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ModalDialog
|
||||||
|
},
|
||||||
|
store: () => store,
|
||||||
|
computed: {
|
||||||
|
emojis: ($currentCustomEmoji) => {
|
||||||
|
if (!$currentCustomEmoji) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return $currentCustomEmoji.filter(emoji => emoji.visible_in_picker)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async show() {
|
||||||
|
this.set({shown: true})
|
||||||
|
},
|
||||||
|
onClickEmoji(emoji) {
|
||||||
|
insertEmoji(emoji)
|
||||||
|
this.set({closed: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,11 +1,16 @@
|
||||||
<div class="modal-dialog-backdrop" tabindex="-1" data-a11y-dialog-hide></div>
|
<div class="modal-dialog-backdrop" tabindex="-1" data-a11y-dialog-hide></div>
|
||||||
<div class="modal-dialog-contents" role="dialog" aria-label="{{label}}" ref:node>
|
<div class="modal-dialog-contents" role="dialog" aria-label="{{label}}" ref:node>
|
||||||
<div class="modal-dialog-document" role="document" style="background: {{background || '#000'}};">
|
<div class="modal-dialog-document" role="document" style="background: {{background || '#000'}};">
|
||||||
|
<div class="modal-dialog-header">
|
||||||
|
{{#if title}}
|
||||||
|
<h1 class="modal-dialog-title">{{title}}</h1>
|
||||||
|
{{/if}}
|
||||||
<div class="close-dialog-button-wrapper">
|
<div class="close-dialog-button-wrapper">
|
||||||
<button class="close-dialog-button" data-a11y-dialog-hide aria-label="Close dialog">
|
<button class="close-dialog-button" data-a11y-dialog-hide aria-label="Close dialog">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,10 +49,24 @@
|
||||||
max-width: calc(100vw - 20px);
|
max-width: calc(100vw - 20px);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.close-dialog-button-wrapper {
|
.modal-dialog-header {
|
||||||
text-align: right;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--nav-bg)
|
background: var(--nav-bg);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.modal-dialog-title {
|
||||||
|
color: var(--nav-text-color);
|
||||||
|
padding: 2px 0 2px 10px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.close-dialog-button-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
.close-dialog-button {
|
.close-dialog-button {
|
||||||
padding: 0 0 7px;
|
padding: 0 0 7px;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './showConfirmationDialog'
|
export * from './showConfirmationDialog'
|
||||||
export * from './showImageDialog'
|
export * from './showImageDialog'
|
||||||
export * from './showVideoDialog'
|
export * from './showVideoDialog'
|
||||||
|
export * from './showEmojiDialog'
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import EmojiDialog from './EmojiDialog.html'
|
||||||
|
|
||||||
|
export function showEmojiDialog () {
|
||||||
|
let emojiDialog = new EmojiDialog({
|
||||||
|
target: document.getElementById('modal-dialog'),
|
||||||
|
data: {
|
||||||
|
label: 'Emoji dialog',
|
||||||
|
title: 'Custom emoji'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
emojiDialog.show()
|
||||||
|
}
|
|
@ -50,3 +50,11 @@ export async function getLists (instanceName) {
|
||||||
export async function setLists (instanceName, value) {
|
export async function setLists (instanceName, value) {
|
||||||
return setMetaProperty(instanceName, 'lists', value)
|
return setMetaProperty(instanceName, 'lists', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getCustomEmoji (instanceName) {
|
||||||
|
return getMetaProperty(instanceName, 'customEmoji')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setCustomEmoji (instanceName, value) {
|
||||||
|
return setMetaProperty(instanceName, 'customEmoji', value)
|
||||||
|
}
|
||||||
|
|
|
@ -106,4 +106,9 @@ export function instanceComputations (store) {
|
||||||
['composeText', 'currentInstance'],
|
['composeText', 'currentInstance'],
|
||||||
(composeText, currentInstance) => (composeText[currentInstance] || '')
|
(composeText, currentInstance) => (composeText[currentInstance] || '')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
store.compute('currentCustomEmoji',
|
||||||
|
['customEmoji', 'currentInstance'],
|
||||||
|
(customEmoji, currentInstance) => (customEmoji[currentInstance] || [])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,8 @@ export const store = new PinaforeStore({
|
||||||
instanceInfos: {},
|
instanceInfos: {},
|
||||||
statusModifications: {},
|
statusModifications: {},
|
||||||
composeText: {},
|
composeText: {},
|
||||||
rawComposeText: ''
|
rawComposeText: '',
|
||||||
|
customEmoji: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
mixins(PinaforeStore)
|
mixins(PinaforeStore)
|
||||||
|
|
Loading…
Reference in New Issue