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 { store } from '../_store/store'
|
||||
import { updateVerifyCredentialsForInstance } from './instances'
|
||||
import { updateCustomEmojiForInstance } from './emoji'
|
||||
|
||||
const REDIRECT_URI = (typeof location !== 'undefined'
|
||||
? location.origin : 'https://pinafore.social') + '/settings/instances/add'
|
||||
|
@ -85,8 +86,9 @@ async function registerNewInstance (code) {
|
|||
})
|
||||
store.save()
|
||||
switchToTheme('default')
|
||||
// fire off request for account so it's cached
|
||||
updateVerifyCredentialsForInstance(currentRegisteredInstanceName)
|
||||
// fire off these requests so they're cached
|
||||
/* no await */ updateVerifyCredentialsForInstance(currentRegisteredInstanceName)
|
||||
/* no await */ updateCustomEmojiForInstance(currentRegisteredInstanceName)
|
||||
goto('/')
|
||||
}
|
||||
|
||||
|
|
21
routes/_actions/emoji.js
Normal file
21
routes/_actions/emoji.js
Normal file
|
@ -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})
|
||||
}
|
7
routes/_api/emoji.js
Normal file
7
routes/_api/emoji.js
Normal file
|
@ -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"
|
||||
aria-label="{{label}}"
|
||||
aria-pressed="{{!!pressed}}"
|
||||
aria-pressed="{{pressable ? !!pressed : ''}}"
|
||||
class="{{computedClass}}"
|
||||
disabled="{{disabled}}"
|
||||
delegate-key="{{delegateKey}}"
|
||||
on:click
|
||||
>
|
||||
:disabled
|
||||
delegate-key="{{delegateKey}}" >
|
||||
<svg>
|
||||
<use xlink:href="{{href}}" />
|
||||
</svg>
|
||||
|
@ -14,11 +12,10 @@
|
|||
{{else}}
|
||||
<button type="button"
|
||||
aria-label="{{label}}"
|
||||
aria-pressed="{{pressable ? !!pressed : ''}}"
|
||||
class="{{computedClass}}"
|
||||
disabled="{{disabled}}"
|
||||
delegate-key="{{delegateKey}}"
|
||||
on:click
|
||||
>
|
||||
:disabled
|
||||
on:click >
|
||||
<svg>
|
||||
<use xlink:href="{{href}}" />
|
||||
</svg>
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
})
|
||||
|
||||
const saveText = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
|
||||
|
||||
this.observe('rawComposeText', rawComposeText => {
|
||||
let composeText = this.store.get('composeText')
|
||||
let currentInstance = this.store.get('currentInstance')
|
||||
|
@ -47,6 +48,23 @@
|
|||
this.store.set({composeText: composeText})
|
||||
saveText()
|
||||
}, {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() {
|
||||
mark('autosize.destroy()')
|
||||
|
@ -57,6 +75,7 @@
|
|||
computed: {
|
||||
rawComposeText: ($rawComposeText) => $rawComposeText,
|
||||
currentComposeText: ($currentComposeText) => $currentComposeText,
|
||||
emojiToInsert: ($emojiToInsert) => $emojiToInsert
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,5 +1,9 @@
|
|||
<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="Adjust privacy" href="#fa-globe" />
|
||||
<IconButton label="Add content warning" href="#fa-exclamation-triangle" />
|
||||
|
@ -14,9 +18,21 @@
|
|||
</style>
|
||||
<script>
|
||||
import IconButton from '../IconButton.html'
|
||||
import { store } from '../../_store/store'
|
||||
import { updateCustomEmojiForInstance } from '../../_actions/emoji'
|
||||
import { importDialogs } from '../../_utils/asyncModules'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconButton
|
||||
},
|
||||
store: () => store,
|
||||
methods: {
|
||||
async onEmojiClick() {
|
||||
/* no await */ updateCustomEmojiForInstance(this.store.get('currentInstance'))
|
||||
let dialogs = await importDialogs()
|
||||
dialogs.showEmojiDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
82
routes/_components/dialog/EmojiDialog.html
Normal file
82
routes/_components/dialog/EmojiDialog.html
Normal file
|
@ -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,10 +1,15 @@
|
|||
<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-document" role="document" style="background: {{background || '#000'}};">
|
||||
<div class="close-dialog-button-wrapper">
|
||||
<button class="close-dialog-button" data-a11y-dialog-hide aria-label="Close dialog">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<div class="modal-dialog-header">
|
||||
{{#if title}}
|
||||
<h1 class="modal-dialog-title">{{title}}</h1>
|
||||
{{/if}}
|
||||
<div class="close-dialog-button-wrapper">
|
||||
<button class="close-dialog-button" data-a11y-dialog-hide aria-label="Close dialog">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
@ -44,10 +49,24 @@
|
|||
max-width: calc(100vw - 20px);
|
||||
flex: 1;
|
||||
}
|
||||
.close-dialog-button-wrapper {
|
||||
text-align: right;
|
||||
.modal-dialog-header {
|
||||
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 {
|
||||
padding: 0 0 7px;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './showConfirmationDialog'
|
||||
export * from './showImageDialog'
|
||||
export * from './showVideoDialog'
|
||||
export * from './showEmojiDialog'
|
||||
|
|
12
routes/_components/dialog/showEmojiDialog.js
Normal file
12
routes/_components/dialog/showEmojiDialog.js
Normal file
|
@ -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) {
|
||||
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] || '')
|
||||
)
|
||||
|
||||
store.compute('currentCustomEmoji',
|
||||
['customEmoji', 'currentInstance'],
|
||||
(customEmoji, currentInstance) => (customEmoji[currentInstance] || [])
|
||||
)
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ export const store = new PinaforeStore({
|
|||
instanceInfos: {},
|
||||
statusModifications: {},
|
||||
composeText: {},
|
||||
rawComposeText: ''
|
||||
rawComposeText: '',
|
||||
customEmoji: {}
|
||||
})
|
||||
|
||||
mixins(PinaforeStore)
|
||||
|
|
Loading…
Reference in a new issue