add custom emoji modal

This commit is contained in:
Nolan Lawson 2018-02-27 23:18:07 -08:00
parent b6eb997893
commit 18dab36e52
13 changed files with 211 additions and 21 deletions

View file

@ -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('/')
} }

21
routes/_actions/emoji.js Normal file
View 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
View 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)
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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">&times;</span> <span aria-hidden="true">&times;</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;

View file

@ -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'

View 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()
}

View file

@ -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)
}

View file

@ -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] || [])
)
} }

View file

@ -40,7 +40,8 @@ export const store = new PinaforeStore({
instanceInfos: {}, instanceInfos: {},
statusModifications: {}, statusModifications: {},
composeText: {}, composeText: {},
rawComposeText: '' rawComposeText: '',
customEmoji: {}
}) })
mixins(PinaforeStore) mixins(PinaforeStore)