logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
commit: 501208d350a9bd0fbcafb13d70a2fa6182fb8cf3
parent: e55645aec16f083e4eedf6b01954b79689c244f1
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date:   Thu, 26 Sep 2019 05:27:59 +0000

Merge branch 'emoji-selector-update' into 'develop'

Emoji selector update

Closes #101

See merge request pleroma/pleroma-fe!895

Diffstat:

ACHANGELOG.md13+++++++++++++
Mdocs/USER_GUIDE.md9+++++++++
Adocs/example_emoji.png0
Msrc/boot/after_store.js12+++++++-----
Dsrc/components/emoji-input/emoji-input.js250-------------------------------------------------------------------------------
Dsrc/components/emoji-input/emoji-input.vue117-------------------------------------------------------------------------------
Asrc/components/emoji_input/emoji_input.js431+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/emoji_input/emoji_input.vue164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/components/emoji-input/suggestor.js -> src/components/emoji_input/suggestor.js0
Asrc/components/emoji_picker/emoji_picker.js115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/emoji_picker/emoji_picker.scss165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/emoji_picker/emoji_picker.vue110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/media_upload/media_upload.vue16+++++++++-------
Msrc/components/post_status_form/post_status_form.js117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/components/post_status_form/post_status_form.vue80+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/components/settings/settings.js4++++
Msrc/components/settings/settings.vue8++++++++
Msrc/components/status/status.vue18+++++++++++-------
Msrc/components/sticker_picker/sticker_picker.js4++--
Msrc/components/sticker_picker/sticker_picker.vue72++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/components/tab_switcher/tab_switcher.js26+++++++++++++++++++++++---
Msrc/components/tab_switcher/tab_switcher.scss11+++++++++++
Msrc/components/user_card/user_card.js11+----------
Msrc/components/user_card/user_card.vue33+++++++++++++++++++++++++++++----
Msrc/components/user_panel/user_panel.vue2+-
Msrc/components/user_settings/user_settings.js4++--
Msrc/components/user_settings/user_settings.vue2++
Msrc/i18n/en.json11+++++++++--
Msrc/modules/config.js1+
Asrc/services/offset_finder/offset_finder.service.js31+++++++++++++++++++++++++++++++
Mstatic/font/LICENSE.txt0
Mstatic/font/README.txt0
Mstatic/font/config.json6++++++
Mstatic/font/css/animation.css0
Mstatic/font/css/fontello-codes.css1+
Mstatic/font/css/fontello-embedded.css14+++++++-------
Mstatic/font/css/fontello-ie7-codes.css1+
Mstatic/font/css/fontello-ie7.css1+
Mstatic/font/css/fontello.css16++++++++--------
Mstatic/font/demo.html16++++++++--------
Mstatic/font/font/fontello.eot0
Mstatic/font/font/fontello.svg2++
Mstatic/font/font/fontello.ttf0
Mstatic/font/font/fontello.woff0
Mstatic/font/font/fontello.woff20
Atest/unit/specs/components/emoji_input.spec.js131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
46 files changed, 1503 insertions(+), 522 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + +## [Unreleased] +### Added +- Emoji picker +- Started changelog anew +### Changed +- changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes +### Fixed +- improved hotkey behavior on autocomplete popup diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md @@ -23,6 +23,15 @@ Posts will contain the text you are posting, but some content will be modified: **Depending on your instance some of the options might not be available or have different defaults** Let's clear up some basic stuff. When you post something it's called a **post** or it could be called a **status** or even a **toot** or a **prööt** depending on whom you ask. Post has body/content but it also has some other stuff in it - from attachments, visibility scope, subject line. +* **Emoji** are small images embedded in text, there are two major types of emoji: [unicode emoji](https://en.wikipedia.org/wiki/Emoji) and custom emoji. While unicode emoji are universal and standardized, they can appear differently depending on where you are using them or may not appear at all on older systems. Custom emoji are more *fun* kind - instance administrator can define many images as *custom emoji* for their users. This works very simple - custom emoji is defined by its *shortcode* and an image, so that any shortcode enclosed in colons get replaced with image if such shortcode exist. +Let's say there's `:pleroma:` emoji defined on instance. That means +> First time using :pleroma: pleroma! + +will become +> First time using ![pleroma](./example_emoji.png) pleroma! + +Note that you can only use emoji defined on your instance, you cannot "copy" someone else's emoji, and will have to ask your administrator to copy emoji from other instance to yours. +Lastly, there's two convenience options for emoji: an emoji picker (smiley face to the right of "submit" button) and autocomplete suggestions - when you start typing :shortcode: it will automatically try to suggest you emoj and complete the shortcode for you if you select one. **Note** that if emoji doesn't show up in suggestions nor in emoji picker it means there's no such emoji on your instance, if shortcode doesn't match any defined emoji it will appear as text. * **Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly. * **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. As a side-effect using subject line will also mark your images as sensitive (see above). * **Visiblity scope** controls who will be able to see your posts. There are four scopes available: diff --git a/docs/example_emoji.png b/docs/example_emoji.png Binary files differ. diff --git a/src/boot/after_store.js b/src/boot/after_store.js @@ -184,7 +184,7 @@ const getStaticEmoji = async ({ store }) => { imageUrl: false, replacement: values[key] } - }) + }).sort((a, b) => a.displayText - b.displayText) store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) } else { throw (res) @@ -203,14 +203,16 @@ const getCustomEmoji = async ({ store }) => { if (res.ok) { const result = await res.json() const values = Array.isArray(result) ? Object.assign({}, ...result) : result - const emoji = Object.keys(values).map((key) => { - const imageUrl = values[key].image_url + const emoji = Object.entries(values).map(([key, value]) => { + const imageUrl = value.image_url return { displayText: key, - imageUrl: imageUrl ? store.state.instance.server + imageUrl : values[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 { diff --git a/src/components/emoji-input/emoji-input.js b/src/components/emoji-input/emoji-input.js @@ -1,250 +0,0 @@ -import Completion from '../../services/completion/completion.js' -import { take } from 'lodash' - -/** - * EmojiInput - augmented inputs for emoji and autocomplete support in inputs - * without having to give up the comfort of <input/> and <textarea/> elements - * - * Intended usage is: - * <EmojiInput v-model="something"> - * <input v-model="something"/> - * </EmojiInput> - * - * Works only with <input> and <textarea>. Intended to use with only one nested - * input. It will find first input or textarea and work with that, multiple - * nested children not tested. You HAVE TO duplicate v-model for both - * <emoji-input> and <input>/<textarea> otherwise it will not work. - * - * Be prepared for CSS troubles though because it still wraps component in a div - * while TRYING to make it look like nothing happened, but it could break stuff. - */ - -const EmojiInput = { - props: { - suggest: { - /** - * suggest: function (input: String) => Suggestion[] - * - * Function that takes input string which takes string (textAtCaret) - * and returns an array of Suggestions - * - * Suggestion is an object containing following properties: - * displayText: string. Main display text, what actual suggestion - * represents (user's screen name/emoji shortcode) - * replacement: string. Text that should replace the textAtCaret - * detailText: string, optional. Subtitle text, providing additional info - * if present (user's nickname) - * imageUrl: string, optional. Image to display alongside with suggestion, - * currently if no image is provided, replacement will be used (for - * unicode emojis) - * - * TODO: make it asynchronous when adding proper server-provided user - * suggestions - * - * For commonly used suggestors (emoji, users, both) use suggestor.js - */ - required: true, - type: Function - }, - value: { - /** - * Used for v-model - */ - required: true, - type: String - } - }, - data () { - return { - input: undefined, - highlighted: 0, - caret: 0, - focused: false, - blurTimeout: null - } - }, - computed: { - suggestions () { - const firstchar = this.textAtCaret.charAt(0) - if (this.textAtCaret === firstchar) { return [] } - const matchedSuggestions = this.suggest(this.textAtCaret) - if (matchedSuggestions.length <= 0) { - return [] - } - return take(matchedSuggestions, 5) - .map(({ imageUrl, ...rest }, index) => ({ - ...rest, - // eslint-disable-next-line camelcase - img: imageUrl || '', - highlighted: index === this.highlighted - })) - }, - showPopup () { - return this.focused && this.suggestions && this.suggestions.length > 0 - }, - textAtCaret () { - return (this.wordAtCaret || {}).word || '' - }, - wordAtCaret () { - if (this.value && this.caret) { - const word = Completion.wordAtPosition(this.value, this.caret - 1) || {} - return word - } - } - }, - mounted () { - const slots = this.$slots.default - if (!slots || slots.length === 0) return - const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag)) - if (!input) return - this.input = input - this.resize() - input.elm.addEventListener('blur', this.onBlur) - input.elm.addEventListener('focus', this.onFocus) - input.elm.addEventListener('paste', this.onPaste) - input.elm.addEventListener('keyup', this.onKeyUp) - input.elm.addEventListener('keydown', this.onKeyDown) - input.elm.addEventListener('transitionend', this.onTransition) - input.elm.addEventListener('compositionupdate', this.onCompositionUpdate) - }, - unmounted () { - const { input } = this - if (input) { - input.elm.removeEventListener('blur', this.onBlur) - input.elm.removeEventListener('focus', this.onFocus) - input.elm.removeEventListener('paste', this.onPaste) - input.elm.removeEventListener('keyup', this.onKeyUp) - input.elm.removeEventListener('keydown', this.onKeyDown) - input.elm.removeEventListener('transitionend', this.onTransition) - input.elm.removeEventListener('compositionupdate', this.onCompositionUpdate) - } - }, - methods: { - replace (replacement) { - const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) - this.$emit('input', newValue) - this.caret = 0 - }, - replaceText (e, suggestion) { - const len = this.suggestions.length || 0 - if (this.textAtCaret.length === 1) { return } - if (len > 0 || suggestion) { - const chosenSuggestion = suggestion || this.suggestions[this.highlighted] - const replacement = chosenSuggestion.replacement - const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) - this.$emit('input', newValue) - this.highlighted = 0 - const position = this.wordAtCaret.start + replacement.length - - this.$nextTick(function () { - // Re-focus inputbox after clicking suggestion - this.input.elm.focus() - // Set selection right after the replacement instead of the very end - this.input.elm.setSelectionRange(position, position) - this.caret = position - }) - e.preventDefault() - } - }, - cycleBackward (e) { - const len = this.suggestions.length || 0 - if (len > 0) { - this.highlighted -= 1 - if (this.highlighted < 0) { - this.highlighted = this.suggestions.length - 1 - } - e.preventDefault() - } else { - this.highlighted = 0 - } - }, - cycleForward (e) { - const len = this.suggestions.length || 0 - if (len > 0) { - this.highlighted += 1 - if (this.highlighted >= len) { - this.highlighted = 0 - } - e.preventDefault() - } else { - this.highlighted = 0 - } - }, - onTransition (e) { - this.resize() - }, - onBlur (e) { - // Clicking on any suggestion removes focus from autocomplete, - // preventing click handler ever executing. - this.blurTimeout = setTimeout(() => { - this.focused = false - this.setCaret(e) - this.resize() - }, 200) - }, - onClick (e, suggestion) { - this.replaceText(e, suggestion) - }, - onFocus (e) { - if (this.blurTimeout) { - clearTimeout(this.blurTimeout) - this.blurTimeout = null - } - - this.focused = true - this.setCaret(e) - this.resize() - }, - onKeyUp (e) { - this.setCaret(e) - this.resize() - }, - onPaste (e) { - this.setCaret(e) - this.resize() - }, - onKeyDown (e) { - this.setCaret(e) - this.resize() - - const { ctrlKey, shiftKey, key } = e - if (key === 'Tab') { - if (shiftKey) { - this.cycleBackward(e) - } else { - this.cycleForward(e) - } - } - if (key === 'ArrowUp') { - this.cycleBackward(e) - } else if (key === 'ArrowDown') { - this.cycleForward(e) - } - if (key === 'Enter') { - if (!ctrlKey) { - this.replaceText(e) - } - } - }, - onInput (e) { - this.setCaret(e) - this.$emit('input', e.target.value) - }, - onCompositionUpdate (e) { - this.setCaret(e) - this.resize() - this.$emit('input', e.target.value) - }, - setCaret ({ target: { selectionStart } }) { - this.caret = selectionStart - }, - resize () { - const { panel } = this.$refs - if (!panel) return - const { offsetHeight, offsetTop } = this.input.elm - this.$refs.panel.style.top = (offsetTop + offsetHeight) + 'px' - } - } -} - -export default EmojiInput diff --git a/src/components/emoji-input/emoji-input.vue b/src/components/emoji-input/emoji-input.vue @@ -1,117 +0,0 @@ -<template> - <div class="emoji-input"> - <slot /> - <div - ref="panel" - class="autocomplete-panel" - :class="{ hide: !showPopup }" - > - <div class="autocomplete-panel-body"> - <div - v-for="(suggestion, index) in suggestions" - :key="index" - class="autocomplete-item" - :class="{ highlighted: suggestion.highlighted }" - @click.stop.prevent="onClick($event, suggestion)" - > - <span class="image"> - <img - v-if="suggestion.img" - :src="suggestion.img" - > - <span v-else>{{ suggestion.replacement }}</span> - </span> - <div class="label"> - <span class="displayText">{{ suggestion.displayText }}</span> - <span class="detailText">{{ suggestion.detailText }}</span> - </div> - </div> - </div> - </div> - </div> -</template> - -<script src="./emoji-input.js"></script> - -<style lang="scss"> -@import '../../_variables.scss'; - -.emoji-input { - display: flex; - flex-direction: column; - - .autocomplete { - &-panel { - position: absolute; - z-index: 9; - margin-top: 2px; - - &.hide { - display: none - } - - &-body { - margin: 0 0.5em 0 0.5em; - border-radius: $fallback--tooltipRadius; - border-radius: var(--tooltipRadius, $fallback--tooltipRadius); - box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5); - box-shadow: var(--popupShadow); - min-width: 75%; - background: $fallback--bg; - background: var(--bg, $fallback--bg); - color: $fallback--lightText; - color: var(--lightText, $fallback--lightText); - } - } - - &-item { - display: flex; - cursor: pointer; - padding: 0.2em 0.4em; - border-bottom: 1px solid rgba(0, 0, 0, 0.4); - height: 32px; - - .image { - width: 32px; - height: 32px; - line-height: 32px; - text-align: center; - font-size: 32px; - - margin-right: 4px; - - img { - width: 32px; - height: 32px; - object-fit: contain; - } - } - - .label { - display: flex; - flex-direction: column; - justify-content: center; - margin: 0 0.1em 0 0.2em; - - .displayText { - line-height: 1.5; - } - - .detailText { - font-size: 9px; - line-height: 9px; - } - } - - &.highlighted { - background-color: $fallback--fg; - background-color: var(--lightBg, $fallback--fg); - } - } - } - - input, textarea { - flex: 1 0 auto; - } -} -</style> diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js @@ -0,0 +1,431 @@ +import Completion from '../../services/completion/completion.js' +import EmojiPicker from '../emoji_picker/emoji_picker.vue' +import { take } from 'lodash' +import { findOffset } from '../../services/offset_finder/offset_finder.service.js' + +/** + * EmojiInput - augmented inputs for emoji and autocomplete support in inputs + * without having to give up the comfort of <input/> and <textarea/> elements + * + * Intended usage is: + * <EmojiInput v-model="something"> + * <input v-model="something"/> + * </EmojiInput> + * + * Works only with <input> and <textarea>. Intended to use with only one nested + * input. It will find first input or textarea and work with that, multiple + * nested children not tested. You HAVE TO duplicate v-model for both + * <emoji-input> and <input>/<textarea> otherwise it will not work. + * + * Be prepared for CSS troubles though because it still wraps component in a div + * while TRYING to make it look like nothing happened, but it could break stuff. + */ + +const EmojiInput = { + props: { + suggest: { + /** + * suggest: function (input: String) => Suggestion[] + * + * Function that takes input string which takes string (textAtCaret) + * and returns an array of Suggestions + * + * Suggestion is an object containing following properties: + * displayText: string. Main display text, what actual suggestion + * represents (user's screen name/emoji shortcode) + * replacement: string. Text that should replace the textAtCaret + * detailText: string, optional. Subtitle text, providing additional info + * if present (user's nickname) + * imageUrl: string, optional. Image to display alongside with suggestion, + * currently if no image is provided, replacement will be used (for + * unicode emojis) + * + * TODO: make it asynchronous when adding proper server-provided user + * suggestions + * + * For commonly used suggestors (emoji, users, both) use suggestor.js + */ + required: true, + type: Function + }, + value: { + /** + * Used for v-model + */ + required: true, + type: String + }, + enableEmojiPicker: { + /** + * Enables emoji picker support, this implies that custom emoji are supported + */ + required: false, + type: Boolean, + default: false + }, + hideEmojiButton: { + /** + * intended to use with external picker trigger, i.e. you have a button outside + * input that will open up the picker, see triggerShowPicker() + */ + required: false, + type: Boolean, + default: false + }, + enableStickerPicker: { + /** + * Enables sticker picker support, only makes sense when enableEmojiPicker=true + */ + required: false, + type: Boolean, + default: false + } + }, + data () { + return { + input: undefined, + highlighted: 0, + caret: 0, + focused: false, + blurTimeout: null, + showPicker: false, + temporarilyHideSuggestions: false, + keepOpen: false, + disableClickOutside: false + } + }, + components: { + EmojiPicker + }, + computed: { + padEmoji () { + return this.$store.state.config.padEmoji + }, + suggestions () { + const firstchar = this.textAtCaret.charAt(0) + if (this.textAtCaret === firstchar) { return [] } + const matchedSuggestions = this.suggest(this.textAtCaret) + if (matchedSuggestions.length <= 0) { + return [] + } + return take(matchedSuggestions, 5) + .map(({ imageUrl, ...rest }, index) => ({ + ...rest, + // eslint-disable-next-line camelcase + img: imageUrl || '', + highlighted: index === this.highlighted + })) + }, + showSuggestions () { + return this.focused && + this.suggestions && + this.suggestions.length > 0 && + !this.showPicker && + !this.temporarilyHideSuggestions + }, + textAtCaret () { + return (this.wordAtCaret || {}).word || '' + }, + wordAtCaret () { + if (this.value && this.caret) { + const word = Completion.wordAtPosition(this.value, this.caret - 1) || {} + return word + } + } + }, + mounted () { + const slots = this.$slots.default + if (!slots || slots.length === 0) return + const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag)) + if (!input) return + this.input = input + this.resize() + input.elm.addEventListener('blur', this.onBlur) + input.elm.addEventListener('focus', this.onFocus) + input.elm.addEventListener('paste', this.onPaste) + input.elm.addEventListener('keyup', this.onKeyUp) + input.elm.addEventListener('keydown', this.onKeyDown) + input.elm.addEventListener('click', this.onClickInput) + input.elm.addEventListener('transitionend', this.onTransition) + input.elm.addEventListener('compositionupdate', this.onCompositionUpdate) + }, + unmounted () { + const { input } = this + if (input) { + input.elm.removeEventListener('blur', this.onBlur) + input.elm.removeEventListener('focus', this.onFocus) + input.elm.removeEventListener('paste', this.onPaste) + input.elm.removeEventListener('keyup', this.onKeyUp) + input.elm.removeEventListener('keydown', this.onKeyDown) + input.elm.removeEventListener('click', this.onClickInput) + input.elm.removeEventListener('transitionend', this.onTransition) + input.elm.removeEventListener('compositionupdate', this.onCompositionUpdate) + } + }, + methods: { + triggerShowPicker () { + this.showPicker = true + this.$nextTick(() => { + this.scrollIntoView() + }) + // This temporarily disables "click outside" handler + // since external trigger also means click originates + // from outside, thus preventing picker from opening + this.disableClickOutside = true + setTimeout(() => { + this.disableClickOutside = false + }, 0) + }, + togglePicker () { + this.input.elm.focus() + this.showPicker = !this.showPicker + if (this.showPicker) { + this.scrollIntoView() + } + }, + replace (replacement) { + const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) + this.$emit('input', newValue) + this.caret = 0 + }, + insert ({ insertion, keepOpen }) { + const before = this.value.substring(0, this.caret) || '' + const after = this.value.substring(this.caret) || '' + + /* Using a bit more smart approach to padding emojis with spaces: + * - put a space before cursor if there isn't one already, unless we + * are at the beginning of post or in spam mode + * - put a space after emoji if there isn't one already unless we are + * in spam mode + * + * The idea is that when you put a cursor somewhere in between sentence + * inserting just ' :emoji: ' will add more spaces to post which might + * break the flow/spacing, as well as the case where user ends sentence + * with a space before adding emoji. + * + * Spam mode is intended for creating multi-part emojis and overall spamming + * them, masto seem to be rendering :emoji::emoji: correctly now so why not + */ + const isSpaceRegex = /\s/ + const spaceBefore = !isSpaceRegex.exec(before.slice(-1)) && before.length && this.padEmoji > 0 ? ' ' : '' + const spaceAfter = !isSpaceRegex.exec(after[0]) && this.padEmoji ? ' ' : '' + + const newValue = [ + before, + spaceBefore, + insertion, + spaceAfter, + after + ].join('') + this.keepOpen = keepOpen + this.$emit('input', newValue) + const position = this.caret + (insertion + spaceAfter + spaceBefore).length + if (!keepOpen) { + this.input.elm.focus() + } + + this.$nextTick(function () { + // Re-focus inputbox after clicking suggestion + // Set selection right after the replacement instead of the very end + this.input.elm.setSelectionRange(position, position) + this.caret = position + }) + }, + replaceText (e, suggestion) { + const len = this.suggestions.length || 0 + if (this.textAtCaret.length === 1) { return } + if (len > 0 || suggestion) { + const chosenSuggestion = suggestion || this.suggestions[this.highlighted] + const replacement = chosenSuggestion.replacement + const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) + this.$emit('input', newValue) + this.highlighted = 0 + const position = this.wordAtCaret.start + replacement.length + + this.$nextTick(function () { + // Re-focus inputbox after clicking suggestion + this.input.elm.focus() + // Set selection right after the replacement instead of the very end + this.input.elm.setSelectionRange(position, position) + this.caret = position + }) + e.preventDefault() + } + }, + cycleBackward (e) { + const len = this.suggestions.length || 0 + if (len > 1) { + this.highlighted -= 1 + if (this.highlighted < 0) { + this.highlighted = this.suggestions.length - 1 + } + e.preventDefault() + } else { + this.highlighted = 0 + } + }, + cycleForward (e) { + const len = this.suggestions.length || 0 + if (len > 1) { + this.highlighted += 1 + if (this.highlighted >= len) { + this.highlighted = 0 + } + e.preventDefault() + } else { + this.highlighted = 0 + } + }, + scrollIntoView () { + const rootRef = this.$refs['picker'].$el + /* 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 scrollerRef = this.$el.closest('.sidebar-scroller') || + this.$el.closest('.post-form-modal-view') || + window + const currentScroll = scrollerRef === window + ? scrollerRef.scrollY + : scrollerRef.scrollTop + const scrollerHeight = scrollerRef === window + ? scrollerRef.innerHeight + : scrollerRef.offsetHeight + + const scrollerBottomBorder = currentScroll + scrollerHeight + // We check where the bottom border of root element is, this uses findOffset + // to find offset relative to scrollable container (scroller) + const rootBottomBorder = rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top + + const bottomDelta = Math.max(0, rootBottomBorder - scrollerBottomBorder) + // could also check top delta but there's no case for it + const targetScroll = currentScroll + bottomDelta + + if (scrollerRef === window) { + scrollerRef.scroll(0, targetScroll) + } else { + scrollerRef.scrollTop = targetScroll + } + }, + onTransition (e) { + this.resize() + }, + onBlur (e) { + // Clicking on any suggestion removes focus from autocomplete, + // preventing click handler ever executing. + this.blurTimeout = setTimeout(() => { + this.focused = false + this.setCaret(e) + this.resize() + }, 200) + }, + onClick (e, suggestion) { + this.replaceText(e, suggestion) + }, + onFocus (e) { + if (this.blurTimeout) { + clearTimeout(this.blurTimeout) + this.blurTimeout = null + } + + if (!this.keepOpen) { + this.showPicker = false + } + this.focused = true + this.setCaret(e) + this.resize() + this.temporarilyHideSuggestions = false + }, + onKeyUp (e) { + const { key } = e + this.setCaret(e) + this.resize() + + // Setting hider in keyUp to prevent suggestions from blinking + // when moving away from suggested spot + if (key === 'Escape') { + this.temporarilyHideSuggestions = true + } else { + this.temporarilyHideSuggestions = false + } + }, + onPaste (e) { + this.setCaret(e) + this.resize() + }, + onKeyDown (e) { + const { ctrlKey, shiftKey, key } = e + // Disable suggestions hotkeys if suggestions are hidden + if (!this.temporarilyHideSuggestions) { + if (key === 'Tab') { + if (shiftKey) { + this.cycleBackward(e) + } else { + this.cycleForward(e) + } + } + if (key === 'ArrowUp') { + this.cycleBackward(e) + } else if (key === 'ArrowDown') { + this.cycleForward(e) + } + if (key === 'Enter') { + if (!ctrlKey) { + this.replaceText(e) + } + } + } + // Probably add optional keyboard controls for emoji picker? + + // Escape hides suggestions, if suggestions are hidden it + // de-focuses the element (i.e. default browser behavior) + if (key === 'Escape') { + if (!this.temporarilyHideSuggestions) { + this.input.elm.focus() + } + } + + this.showPicker = false + this.resize() + }, + onInput (e) { + this.showPicker = false + this.setCaret(e) + this.resize() + this.$emit('input', e.target.value) + }, + onCompositionUpdate (e) { + this.showPicker = false + this.setCaret(e) + this.resize() + this.$emit('input', e.target.value) + }, + onClickInput (e) { + this.showPicker = false + }, + onClickOutside (e) { + if (this.disableClickOutside) return + this.showPicker = false + }, + onStickerUploaded (e) { + this.showPicker = false + this.$emit('sticker-uploaded', e) + }, + onStickerUploadFailed (e) { + this.showPicker = false + this.$emit('sticker-upload-Failed', e) + }, + setCaret ({ target: { selectionStart } }) { + this.caret = selectionStart + }, + resize () { + const { panel } = 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' + } + } +} + +export default EmojiInput diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue @@ -0,0 +1,164 @@ +<template> + <div + v-click-outside="onClickOutside" + class="emoji-input" + > + <slot /> + <template v-if="enableEmojiPicker"> + <div + v-if="!hideEmojiButton" + class="emoji-picker-icon" + @click.prevent="togglePicker" + > + <i class="icon-smile" /> + </div> + <EmojiPicker + v-if="enableEmojiPicker" + ref="picker" + :class="{ hide: !showPicker }" + :enable-sticker-picker="enableStickerPicker" + class="emoji-picker-panel" + @emoji="insert" + @sticker-uploaded="onStickerUploaded" + @sticker-upload-failed="onStickerUploadFailed" + /> + </template> + <div + ref="panel" + class="autocomplete-panel" + :class="{ hide: !showSuggestions }" + > + <div class="autocomplete-panel-body"> + <div + v-for="(suggestion, index) in suggestions" + :key="index" + class="autocomplete-item" + :class="{ highlighted: suggestion.highlighted }" + @click.stop.prevent="onClick($event, suggestion)" + > + <span class="image"> + <img + v-if="suggestion.img" + :src="suggestion.img" + > + <span v-else>{{ suggestion.replacement }}</span> + </span> + <div class="label"> + <span class="displayText">{{ suggestion.displayText }}</span> + <span class="detailText">{{ suggestion.detailText }}</span> + </div> + </div> + </div> + </div> + </div> +</template> + +<script src="./emoji_input.js"></script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.emoji-input { + display: flex; + flex-direction: column; + position: relative; + + .emoji-picker-icon { + position: absolute; + top: 0; + right: 0; + margin: .2em .25em; + font-size: 16px; + cursor: pointer; + line-height: 24px; + + &:hover i { + color: $fallback--text; + color: var(--text, $fallback--text); + } + } + .emoji-picker-panel { + position: absolute; + z-index: 20; + margin-top: 2px; + + &.hide { + display: none + } + } + + .autocomplete { + &-panel { + position: absolute; + z-index: 20; + margin-top: 2px; + + &.hide { + display: none + } + + &-body { + margin: 0 0.5em 0 0.5em; + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5); + box-shadow: var(--popupShadow); + min-width: 75%; + background: $fallback--bg; + background: var(--bg, $fallback--bg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); + } + } + + &-item { + display: flex; + cursor: pointer; + padding: 0.2em 0.4em; + border-bottom: 1px solid rgba(0, 0, 0, 0.4); + height: 32px; + + .image { + width: 32px; + height: 32px; + line-height: 32px; + text-align: center; + font-size: 32px; + + margin-right: 4px; + + img { + width: 32px; + height: 32px; + object-fit: contain; + } + } + + .label { + display: flex; + flex-direction: column; + justify-content: center; + margin: 0 0.1em 0 0.2em; + + .displayText { + line-height: 1.5; + } + + .detailText { + font-size: 9px; + line-height: 9px; + } + } + + &.highlighted { + background-color: $fallback--fg; + background-color: var(--lightBg, $fallback--fg); + } + } + } + + input, textarea { + flex: 1 0 auto; + } +} +</style> diff --git a/src/components/emoji-input/suggestor.js b/src/components/emoji_input/suggestor.js diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js @@ -0,0 +1,115 @@ + +const filterByKeyword = (list, keyword = '') => { + return list.filter(x => x.displayText.includes(keyword)) +} + +const EmojiPicker = { + props: { + enableStickerPicker: { + required: false, + type: Boolean, + default: false + } + }, + data () { + return { + labelKey: String(Math.random() * 100000), + keyword: '', + activeGroup: 'custom', + showingStickers: false, + groupsScrolledClass: 'scrolled-top', + keepOpen: false + } + }, + components: { + StickerPicker: () => import('../sticker_picker/sticker_picker.vue') + }, + methods: { + onEmoji (emoji) { + const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement + this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen }) + }, + highlight (key) { + const ref = this.$refs['group-' + key] + const top = ref[0].offsetTop + this.setShowStickers(false) + this.activeGroup = key + this.$nextTick(() => { + this.$refs['emoji-groups'].scrollTop = top + 1 + }) + }, + scrolledGroup (e) { + const target = (e && e.target) || this.$refs['emoji-groups'] + const top = target.scrollTop + 5 + if (target.scrollTop <= 5) { + this.groupsScrolledClass = 'scrolled-top' + } else if (target.scrollTop >= target.scrollTopMax - 5) { + this.groupsScrolledClass = 'scrolled-bottom' + } else { + this.groupsScrolledClass = 'scrolled-middle' + } + this.$nextTick(() => { + this.emojisView.forEach(group => { + const ref = this.$refs['group-' + group.id] + if (ref[0].offsetTop <= top) { + this.activeGroup = group.id + } + }) + }) + }, + 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() + } + }, + computed: { + activeGroupView () { + return this.showingStickers ? '' : this.activeGroup + }, + stickersAvailable () { + if (this.$store.state.instance.stickers) { + return this.$store.state.instance.stickers.length > 0 + } + return 0 + }, + emojis () { + const standardEmojis = this.$store.state.instance.emoji || [] + const customEmojis = this.$store.state.instance.customEmoji || [] + return [ + { + id: 'custom', + text: this.$t('emoji.custom'), + icon: 'icon-smile', + emojis: filterByKeyword(customEmojis, this.keyword) + }, + { + id: 'standard', + text: this.$t('emoji.unicode'), + icon: 'icon-picture', + emojis: filterByKeyword(standardEmojis, this.keyword) + } + ] + }, + emojisView () { + return this.emojis.filter(value => value.emojis.length > 0) + }, + stickerPickerEnabled () { + return (this.$store.state.instance.stickers || []).length !== 0 + } + } +} + +export default EmojiPicker diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss @@ -0,0 +1,165 @@ +@import '../../_variables.scss'; + +.emoji-picker { + display: flex; + flex-direction: column; + position: absolute; + right: 0; + left: 0; + height: 320px; + margin: 0 !important; + z-index: 1; + + .keep-open { + padding: 7px; + line-height: normal; + } + .keep-open-label { + padding: 0 7px; + display: flex; + } + + .heading { + display: flex; + height: 32px; + padding: 10px 7px 5px; + } + + .content { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-height: 0px; + } + + .emoji-tabs { + flex-grow: 1; + } + + .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; + } + + .additional-tabs, + .emoji-tabs { + display: block; + min-width: 0; + flex-basis: auto; + flex-shrink: 1; + + &-item { + padding: 0 7px; + cursor: pointer; + font-size: 24px; + + &.disabled { + opacity: 0.5; + pointer-events: none; + } + &.active { + border-bottom: 4px solid; + + i { + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); + } + } + } + } + + .sticker-picker { + flex: 1 1 0 + } + + .stickers, + .emoji { + &-content { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-height: 0; + + &.hidden { + opacity: 0; + pointer-events: none; + position: absolute; + } + } + } + + .emoji { + &-search { + padding: 5px; + flex: 0 0 0; + + input { + 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: 12px; + 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%; + } + } + + } + +} diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue @@ -0,0 +1,110 @@ +<template> + <div class="emoji-picker panel panel-default panel-body"> + <div class="heading"> + <span class="emoji-tabs"> + <span + v-for="group in emojis" + :key="group.id" + class="emoji-tabs-item" + :class="{ + active: activeGroupView === group.id, + disabled: group.emojis.length === 0 + }" + :title="group.text" + @click.prevent="highlight(group.id)" + > + <i :class="group.icon" /> + </span> + </span> + <span + v-if="stickerPickerEnabled" + class="additional-tabs" + > + <span + class="stickers-tab-icon additional-tabs-item" + :class="{active: showingStickers}" + :title="$t('emoji.stickers')" + @click.prevent="toggleStickers" + > + <i class="icon-star" /> + </span> + </span> + </div> + <div class="content"> + <div + class="emoji-content" + :class="{hidden: showingStickers}" + > + <div class="emoji-search"> + <input + v-model="keyword" + type="text" + class="form-control" + :placeholder="$t('emoji.search_emoji')" + > + </div> + <div + ref="emoji-groups" + class="emoji-groups" + :class="groupsScrolledClass" + @scroll="scrolledGroup" + > + <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> + </div> + </div> + <div + class="keep-open" + > + <input + :id="labelKey + 'keep-open'" + v-model="keepOpen" + type="checkbox" + > + <label + class="keep-open-label" + :for="labelKey + 'keep-open'" + > + <div class="keep-open-label-text"> + {{ $t('emoji.keep_open') }} + </div> + </label> + </div> + </div> + <div + v-if="showingStickers" + class="stickers-content" + > + <sticker-picker + @uploaded="onStickerUploaded" + @upload-failed="onStickerUploadFailed" + /> + </div> + </div> + </div> +</template> + +<script src="./emoji_picker.js"></script> +<style lang="scss" src="./emoji_picker.scss"></style> diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue @@ -31,12 +31,14 @@ <script src="./media_upload.js" ></script> <style> - .media-upload { - font-size: 26px; - min-width: 50px; - } +.media-upload { + .icon-upload { + cursor: pointer; + } - .icon-upload { - cursor: pointer; - } + label { + display: block; + width: 100%; + } +} </style> diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js @@ -1,12 +1,12 @@ import statusPoster from '../../services/status_poster/status_poster.service.js' import MediaUpload from '../media_upload/media_upload.vue' import ScopeSelector from '../scope_selector/scope_selector.vue' -import EmojiInput from '../emoji-input/emoji-input.vue' +import EmojiInput from '../emoji_input/emoji_input.vue' import PollForm from '../poll/poll_form.vue' -import StickerPicker from '../sticker_picker/sticker_picker.vue' import fileTypeService from '../../services/file_type/file_type.service.js' +import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { reject, map, uniqBy } from 'lodash' -import suggestor from '../emoji-input/suggestor.js' +import suggestor from '../emoji_input/suggestor.js' const buildMentionsString = ({ user, attentions = [] }, currentUser) => { let allAttentions = [...attentions] @@ -35,7 +35,6 @@ const PostStatusForm = { MediaUpload, EmojiInput, PollForm, - StickerPicker, ScopeSelector }, mounted () { @@ -84,8 +83,7 @@ const PostStatusForm = { contentType }, caret: 0, - pollFormVisible: false, - stickerPickerVisible: false + pollFormVisible: false } }, computed: { @@ -161,12 +159,6 @@ const PostStatusForm = { safeDMEnabled () { return this.$store.state.instance.safeDM }, - stickersAvailable () { - if (this.$store.state.instance.stickers) { - return this.$store.state.instance.stickers.length > 0 - } - return 0 - }, pollsAvailable () { return this.$store.state.instance.pollsAvailable && this.$store.state.instance.pollLimits.max_options >= 2 @@ -222,7 +214,6 @@ const PostStatusForm = { poll: {} } this.pollFormVisible = false - this.stickerPickerVisible = false this.$refs.mediaUpload.clearFile() this.clearPollForm() this.$emit('posted') @@ -239,7 +230,6 @@ const PostStatusForm = { addMediaFile (fileInfo) { this.newStatus.files.push(fileInfo) this.enableSubmit() - this.stickerPickerVisible = false }, removeMediaFile (fileInfo) { let index = this.newStatus.files.indexOf(fileInfo) @@ -260,6 +250,7 @@ const PostStatusForm = { return fileTypeService.fileType(fileInfo.mimetype) }, paste (e) { + this.resize(e) if (e.clipboardData.files.length > 0) { // prevent pasting of file as text e.preventDefault() @@ -278,20 +269,96 @@ const PostStatusForm = { fileDrag (e) { e.dataTransfer.dropEffect = 'copy' }, + onEmojiInputInput (e) { + this.$nextTick(() => { + this.resize(this.$refs['textarea']) + }) + }, resize (e) { const target = e.target || e if (!(target instanceof window.Element)) { return } + + // Reset to default height for empty form, nothing else to do here. + if (target.value === '') { + target.style.height = null + this.$refs['emoji-input'].resize() + return + } + + const rootRef = this.$refs['root'] + /* 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 scrollerRef = this.$el.closest('.sidebar-scroller') || + this.$el.closest('.post-form-modal-view') || + window + + // Getting info about padding we have to account for, removing 'px' part const topPaddingStr = window.getComputedStyle(target)['padding-top'] const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom'] - // Remove "px" at the end of the values - const vertPadding = Number(topPaddingStr.substr(0, topPaddingStr.length - 2)) + - Number(bottomPaddingStr.substr(0, bottomPaddingStr.length - 2)) - // Auto is needed to make textbox shrink when removing lines + const topPadding = Number(topPaddingStr.substring(0, topPaddingStr.length - 2)) + 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 + * scrollHeight returns element's scrollable content height, i.e. visible + * element + overscrolled parts of it. We use it to determine when text + * inside the textarea exceeded its height, so we can set height to prevent + * overscroll, i.e. make textarea grow with the text. HOWEVER, since we + * explicitly set new height, scrollHeight won't go below that, so we can't + * 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 + * 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. + */ + + // this part has to be BEFORE the content size update + const currentScroll = scrollerRef === window + ? scrollerRef.scrollY + : scrollerRef.scrollTop + const scrollerHeight = scrollerRef === window + ? scrollerRef.innerHeight + : scrollerRef.offsetHeight + const scrollerBottomBorder = currentScroll + scrollerHeight + + // BEGIN content size update target.style.height = 'auto' - target.style.height = `${target.scrollHeight - vertPadding}px` - if (target.value === '') { - target.style.height = null + const newHeight = target.scrollHeight - vertPadding + target.style.height = `${newHeight}px` + // END content size update + + // We check where the bottom border of root 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 targetScroll = currentScroll + totalDelta + + if (scrollerRef === window) { + scrollerRef.scroll(0, targetScroll) + } else { + scrollerRef.scrollTop = targetScroll } + + this.$refs['emoji-input'].resize() + }, + showEmojiPicker () { + this.$refs['textarea'].focus() + this.$refs['emoji-input'].triggerShowPicker() }, clearError () { this.error = null @@ -299,14 +366,6 @@ const PostStatusForm = { changeVis (visibility) { this.newStatus.visibility = visibility }, - toggleStickerPicker () { - this.stickerPickerVisible = !this.stickerPickerVisible - }, - clearStickerPicker () { - if (this.$refs.stickerPicker) { - this.$refs.stickerPicker.clear() - } - }, togglePollForm () { this.pollFormVisible = !this.pollFormVisible }, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue @@ -1,5 +1,8 @@ <template> - <div class="post-status-form"> + <div + ref="root" + class="post-status-form" + > <form autocomplete="off" @submit.prevent="postStatus(newStatus)" @@ -61,6 +64,7 @@ <EmojiInput v-if="newStatus.spoilerText || alwaysShowSubject" v-model="newStatus.spoilerText" + enable-emoji-picker :suggest="emojiSuggestor" class="form-control" > @@ -73,9 +77,16 @@ > </EmojiInput> <EmojiInput + ref="emoji-input" v-model="newStatus.status" :suggest="emojiUserSuggestor" class="form-control main-input" + enable-emoji-picker + hide-emoji-button + enable-sticker-picker + @input="onEmojiInputInput" + @sticker-uploaded="addMediaFile" + @sticker-upload-failed="uploadFailed" > <textarea ref="textarea" @@ -89,6 +100,7 @@ @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize" + @compositionupdate="resize" @paste="paste" /> <p @@ -152,30 +164,29 @@ <div class="form-bottom-left"> <media-upload ref="mediaUpload" + class="media-upload-icon" :drop-files="dropFiles" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" /> <div - v-if="stickersAvailable" - class="sticker-icon" + class="emoji-icon" > <i - :title="$t('stickers.add_sticker')" - class="icon-picture btn btn-default" - :class="{ selected: stickerPickerVisible }" - @click="toggleStickerPicker" + :title="$t('emoji.add_emoji')" + class="icon-smile btn btn-default" + @click="showEmojiPicker" /> </div> <div v-if="pollsAvailable" class="poll-icon" + :class="{ selected: pollFormVisible }" > <i :title="$t('polls.add_poll')" class="icon-chart-bar btn btn-default" - :class="pollFormVisible && 'selected'" @click="togglePollForm" /> </div> @@ -258,11 +269,6 @@ <label for="filesSensitive">{{ $t('post_status.attachments_sensitive') }}</label> </div> </form> - <sticker-picker - v-if="stickerPickerVisible" - ref="stickerPicker" - @uploaded="addMediaFile" - /> </div> </template> @@ -299,6 +305,7 @@ .post-status-form { .form-bottom { display: flex; + justify-content: space-between; padding: 0.5em; height: 32px; @@ -316,6 +323,9 @@ .form-bottom-left { display: flex; flex: 1; + padding-right: 7px; + margin-right: 7px; + max-width: 10em; } .text-format { @@ -325,19 +335,38 @@ } } - .poll-icon, .sticker-icon { + .media-upload-icon, .poll-icon, .emoji-icon { font-size: 26px; flex: 1; - .selected { - color: $fallback--lightText; - color: var(--lightText, $fallback--lightText); + i { + display: block; + width: 100%; + } + + &.selected, &:hover { + // needs to be specific to override icon default color + i, label { + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); + } } } - .sticker-icon { - flex: 0; - min-width: 50px; + // Order is not necessary but a good indicator + .media-upload-icon { + order: 1; + text-align: left; + } + + .emoji-icon { + order: 2; + text-align: center; + } + + .poll-icon { + order: 3; + text-align: right; } .icon-chart-bar { @@ -369,6 +398,13 @@ } } + .status-input-wrapper { + display: flex; + position: relative; + width: 100%; + flex-direction: column; + } + .attachments { padding: 0 0.5em; @@ -444,10 +480,6 @@ box-sizing: content-box; } - .form-post-body:focus { - min-height: 48px; - } - .main-input { position: relative; } diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js @@ -16,6 +16,7 @@ const settings = { return { hideAttachmentsLocal: user.hideAttachments, + padEmojiLocal: user.padEmoji, hideAttachmentsInConvLocal: user.hideAttachmentsInConv, maxThumbnails: user.maxThumbnails, hideNsfwLocal: user.hideNsfw, @@ -127,6 +128,9 @@ const settings = { hideAttachmentsLocal (value) { this.$store.dispatch('setOption', { name: 'hideAttachments', value }) }, + padEmojiLocal (value) { + this.$store.dispatch('setOption', { name: 'padEmoji', value }) + }, hideAttachmentsInConvLocal (value) { this.$store.dispatch('setOption', { name: 'hideAttachmentsInConv', value }) }, diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue @@ -198,6 +198,14 @@ > <label for="autohideFloatingPostButton">{{ $t('settings.autohide_floating_post_button') }}</label> </li> + <li> + <input + id="padEmoji" + v-model="padEmojiLocal" + type="checkbox" + > + <label for="padEmoji">{{ $t('settings.pad_emoji') }}</label> + </li> </ul> </div> diff --git a/src/components/status/status.vue b/src/components/status/status.vue @@ -413,7 +413,7 @@ v-if="replying" class="container" > - <post-status-form + <PostStatusForm class="reply-body" :reply-to="status.id" :attentions="status.attentions" @@ -665,6 +665,15 @@ $status-margin: 0.75em; height: 220px; overflow-x: hidden; overflow-y: hidden; + z-index: 1; + .status-content { + height: 100%; + mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat, + linear-gradient(to top, white, white); + // Autoprefixed seem to ignore this one, and also syntax is different + -webkit-mask-composite: xor; + mask-composite: exclude; + } } .tall-status-hider { @@ -676,12 +685,7 @@ $status-margin: 0.75em; width: 100%; text-align: center; line-height: 110px; - background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%); - background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%); - &_focused { - background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--lightBg 80%); - background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--lightBg, $fallback--lightBg) 80%); - } + z-index: 2; } .status-unhider, .cw-status-hider { diff --git a/src/components/sticker_picker/sticker_picker.js b/src/components/sticker_picker/sticker_picker.js @@ -3,9 +3,9 @@ import statusPosterService from '../../services/status_poster/status_poster.serv import TabSwitcher from '../tab_switcher/tab_switcher.js' const StickerPicker = { - components: [ + components: { TabSwitcher - ], + }, data () { return { meta: { diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue @@ -2,32 +2,30 @@ <div class="sticker-picker" > - <div - class="sticker-picker-panel" + <tab-switcher + class="tab-switcher" + :render-only-focused="true" + scrollable-tabs > - <tab-switcher - :render-only-focused="true" + <div + v-for="stickerpack in pack" + :key="stickerpack.path" + :image-tooltip="stickerpack.meta.title" + :image="stickerpack.path + stickerpack.meta.tabIcon" + class="sticker-picker-content" > <div - v-for="stickerpack in pack" - :key="stickerpack.path" - :image-tooltip="stickerpack.meta.title" - :image="stickerpack.path + stickerpack.meta.tabIcon" - class="sticker-picker-content" + v-for="sticker in stickerpack.meta.stickers" + :key="sticker" + class="sticker" + @click.stop.prevent="pick(stickerpack.path + sticker, stickerpack.meta.title)" > - <div - v-for="sticker in stickerpack.meta.stickers" - :key="sticker" - class="sticker" - @click="pick(stickerpack.path + sticker, stickerpack.meta.title)" + <img + :src="stickerpack.path + sticker" > - <img - :src="stickerpack.path + sticker" - > - </div> </div> - </tab-switcher> - </div> + </div> + </tab-switcher> </div> </template> @@ -37,22 +35,24 @@ @import '../../_variables.scss'; .sticker-picker { - .sticker-picker-panel { - display: inline-block; - width: 100%; - .sticker-picker-content { - max-height: 300px; - overflow-y: scroll; - overflow-x: auto; - .sticker { - display: inline-block; - width: 20%; - height: 20%; - img { - width: 100%; - &:hover { - filter: drop-shadow(0 0 5px var(--link, $fallback--link)); - } + width: 100%; + position: relative; + .tab-switcher { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + .sticker-picker-content { + .sticker { + display: inline-block; + width: 20%; + height: 20%; + img { + width: 100%; + &:hover { + filter: drop-shadow(0 0 5px var(--link, $fallback--link)); } } } diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js @@ -4,7 +4,26 @@ import './tab_switcher.scss' export default Vue.component('tab-switcher', { name: 'TabSwitcher', - props: ['renderOnlyFocused', 'onSwitch', 'activeTab'], + props: { + renderOnlyFocused: { + required: false, + type: Boolean, + default: false + }, + onSwitch: { + required: false, + type: Function + }, + activeTab: { + required: false, + type: String + }, + scrollableTabs: { + required: false, + type: Boolean, + default: false + } + }, data () { return { active: this.$slots.default.findIndex(_ => _.tag) @@ -28,7 +47,8 @@ export default Vue.component('tab-switcher', { }, methods: { activateTab (index) { - return () => { + return (e) => { + e.preventDefault() if (typeof this.onSwitch === 'function') { this.onSwitch.call(null, this.$slots.default[index].key) } @@ -87,7 +107,7 @@ export default Vue.component('tab-switcher', { <div class="tabs"> {tabs} </div> - <div class="contents"> + <div class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}> {contents} </div> </div> diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss @@ -1,10 +1,21 @@ @import '../../_variables.scss'; .tab-switcher { + display: flex; + flex-direction: column; + .contents { + flex: 1 0 auto; + min-height: 0px; + .hidden { display: none; } + + &.scrollable-tabs { + flex-basis: 0; + overflow-y: auto; + } } .tabs { display: flex; diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js @@ -37,19 +37,10 @@ export default { const rgb = (typeof color === 'string') ? hex2rgb(color) : color const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)` - const gradient = [ - [tintColor, this.hideBio ? '60%' : ''], - this.hideBio ? [ - color, '100%' - ] : [ - tintColor, '' - ] - ].map(_ => _.join(' ')).join(', ') - return { backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`, backgroundImage: [ - `linear-gradient(to bottom, ${gradient})`, + `linear-gradient(to bottom, ${tintColor}, ${tintColor})`, `url(${this.user.cover_photo})` ].join(', ') } diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue @@ -2,8 +2,12 @@ <div class="user-card" :class="classes" - :style="style" > + <div + :class="{ 'hide-bio': hideBio }" + :style="style" + class="background-image" + /> <div class="panel-heading"> <div class="user-info"> <div class="container"> @@ -307,7 +311,7 @@ @import '../../_variables.scss'; .user-card { - background-size: cover; + position: relative; .panel-heading { padding: .5em 0; @@ -316,14 +320,35 @@ background: transparent; flex-direction: column; align-items: stretch; + // create new stacking context + position: relative; } .panel-body { word-wrap: break-word; - background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%); - background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%); border-bottom-right-radius: inherit; border-bottom-left-radius: inherit; + // create new stacking context + position: relative; + } + + .background-image { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + mask: linear-gradient(to top, white, transparent) bottom no-repeat, + linear-gradient(to top, white, white); + // Autoprefixed seem to ignore this one, and also syntax is different + -webkit-mask-composite: xor; + mask-composite: exclude; + background-size: cover; + mask-size: 100% 60%; + + &.hide-bio { + mask-size: 100% 40px; + } } p { diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue @@ -11,7 +11,7 @@ rounded="top" /> <div class="panel-footer"> - <post-status-form /> + <PostStatusForm /> </div> </div> <auth-form diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js @@ -11,8 +11,8 @@ import BlockCard from '../block_card/block_card.vue' import MuteCard from '../mute_card/mute_card.vue' import SelectableList from '../selectable_list/selectable_list.vue' import ProgressButton from '../progress_button/progress_button.vue' -import EmojiInput from '../emoji-input/emoji-input.vue' -import suggestor from '../emoji-input/suggestor.js' +import EmojiInput from '../emoji_input/emoji_input.vue' +import suggestor from '../emoji_input/suggestor.js' import Autosuggest from '../autosuggest/autosuggest.vue' import Importer from '../importer/importer.vue' import Exporter from '../exporter/exporter.vue' diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue @@ -32,6 +32,7 @@ <p>{{ $t('settings.name') }}</p> <EmojiInput v-model="newName" + enable-emoji-picker :suggest="emojiSuggestor" > <input @@ -43,6 +44,7 @@ <p>{{ $t('settings.bio') }}</p> <EmojiInput v-model="newBio" + enable-emoji-picker :suggest="emojiUserSuggestor" > <textarea diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -106,8 +106,14 @@ "expired": "Poll ended {0} ago", "not_enough_options": "Too few unique options in poll" }, - "stickers": { - "add_sticker": "Add Sticker" + "emoji": { + "stickers": "Stickers", + "emoji": "Emoji", + "keep_open": "Keep picker open", + "search_emoji": "Search for an emoji", + "add_emoji": "Insert emoji", + "custom": "Custom emoji", + "unicode": "Unicode emoji" }, "interactions": { "favs_repeats": "Repeats and Favorites", @@ -226,6 +232,7 @@ "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", "delete_account_instructions": "Type your password in the input below to confirm account deletion.", "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", + "pad_emoji": "Pad emoji with spaces when adding from picker", "export_theme": "Save preset", "filtering": "Filtering", "filtering_explanation": "All statuses containing these words will be muted, one per line", diff --git a/src/modules/config.js b/src/modules/config.js @@ -7,6 +7,7 @@ const defaultState = { colors: {}, hideMutedPosts: undefined, // instance default collapseMessageWithSubject: undefined, // instance default + padEmoji: true, hideAttachments: false, hideAttachmentsInConv: false, maxThumbnails: 16, diff --git a/src/services/offset_finder/offset_finder.service.js b/src/services/offset_finder/offset_finder.service.js @@ -0,0 +1,31 @@ +export const findOffset = (child, parent, { top = 0, left = 0 } = {}, ignorePadding = true) => { + const result = { + top: top + child.offsetTop, + left: left + child.offsetLeft + } + if (!ignorePadding && child !== window) { + const { topPadding, leftPadding } = findPadding(child) + result.top += ignorePadding ? 0 : topPadding + result.left += ignorePadding ? 0 : leftPadding + } + + if (child.offsetParent && (parent === window || parent.contains(child.offsetParent) || parent === child.offsetParent)) { + return findOffset(child.offsetParent, parent, result, false) + } else { + if (parent !== window) { + const { topPadding, leftPadding } = findPadding(parent) + result.top += topPadding + result.left += leftPadding + } + return result + } +} + +const findPadding = (el) => { + const topPaddingStr = window.getComputedStyle(el)['padding-top'] + const topPadding = Number(topPaddingStr.substring(0, topPaddingStr.length - 2)) + const leftPaddingStr = window.getComputedStyle(el)['padding-left'] + const leftPadding = Number(leftPaddingStr.substring(0, leftPaddingStr.length - 2)) + + return { topPadding, leftPadding } +} diff --git a/static/font/LICENSE.txt b/static/font/LICENSE.txt diff --git a/static/font/README.txt b/static/font/README.txt diff --git a/static/font/config.json b/static/font/config.json @@ -241,6 +241,12 @@ "src": "fontawesome" }, { + "uid": "d862a10e1448589215be19702f98f2c1", + "css": "smile", + "code": 61720, + "src": "fontawesome" + }, + { "uid": "671f29fa10dda08074a4c6a341bb4f39", "css": "bell-alt", "code": 61683, diff --git a/static/font/css/animation.css b/static/font/css/animation.css diff --git a/static/font/css/fontello-codes.css b/static/font/css/fontello-codes.css @@ -38,6 +38,7 @@ .icon-bell-alt:before { content: '\f0f3'; } /* '' */ .icon-plus-squared:before { content: '\f0fe'; } /* '' */ .icon-reply:before { content: '\f112'; } /* '' */ +.icon-smile:before { content: '\f118'; } /* '' */ .icon-lock-open-alt:before { content: '\f13e'; } /* '' */ .icon-ellipsis:before { content: '\f141'; } /* '' */ .icon-play-circled:before { content: '\f144'; } /* '' */ diff --git a/static/font/css/fontello-embedded.css b/static/font/css/fontello-embedded.css @@ -1,15 +1,15 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?49712213'); - src: url('../font/fontello.eot?49712213#iefix') format('embedded-opentype'), - url('../font/fontello.svg?49712213#fontello') format('svg'); + src: url('../font/fontello.eot?88512238'); + src: url('../font/fontello.eot?88512238#iefix') format('embedded-opentype'), + url('../font/fontello.svg?88512238#fontello') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'fontello'; - src: url('data:application/octet-stream;base64,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') format('woff'), - url('data:application/octet-stream;base64,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') format('truetype'); + src: url('data:application/octet-stream;base64,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') format('woff'), + url('data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCLJXoAAAD8AAAAVE9TLzI+L1N2AAABUAAAAFZjbWFw75ShDQAAAagAAARYY3Z0IAb//vQAAD7wAAAAIGZwZ22KkZBZAAA/EAAAC3BnYXNwAAAAEAAAPugAAAAIZ2x5ZhBTvwwAAAYAAAAyhmhlYWQWUrHsAAA4iAAAADZoaGVhB8kECQAAOMAAAAAkaG10eKU2/98AADjkAAAAuGxvY2EqxhtSAAA5nAAAAF5tYXhwAYMNpgAAOfwAAAAgbmFtZcydHyEAADocAAACzXBvc3SgAaIJAAA87AAAAftwcmVw5UErvAAASoAAAACGAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDlwGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgA8jQDWf9xAFoDZwCeAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAIsAAEAAAAAASYAAwABAAAALAADAAoAAAIsAAQA+gAAACQAIAAEAAToG+gy6DTwj/DJ8ODw5fDz8P7xEvEY8T7xQfFE8WTx5fI0//8AAOgA6DLoNPCO8Mnw4PDl8PPw/vES8RjxPvFB8UTxZPHl8jT//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAkAFoAWgBaAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAACLAAAAAAAAAAtAADoAAAA6AAAAAABAADoAQAA6AEAAAACAADoAgAA6AIAAAADAADoAwAA6AMAAAAEAADoBAAA6AQAAAAFAADoBQAA6AUAAAAGAADoBgAA6AYAAAAHAADoBwAA6AcAAAAIAADoCAAA6AgAAAAJAADoCQAA6AkAAAAKAADoCgAA6AoAAAALAADoCwAA6AsAAAAMAADoDAAA6AwAAAANAADoDQAA6A0AAAAOAADoDgAA6A4AAAAPAADoDwAA6A8AAAAQAADoEAAA6BAAAAARAADoEQAA6BEAAAASAADoEgAA6BIAAAATAADoEwAA6BMAAAAUAADoFAAA6BQAAAAVAADoFQAA6BUAAAAWAADoFgAA6BYAAAAXAADoFwAA6BcAAAAYAADoGAAA6BgAAAAZAADoGQAA6BkAAAAaAADoGgAA6BoAAAAbAADoGwAA6BsAAAAcAADoMgAA6DIAAAAdAADoNAAA6DQAAAAeAADwjgAA8I4AAAAfAADwjwAA8I8AAAAgAADwyQAA8MkAAAAhAADw4AAA8OAAAAAiAADw5QAA8OUAAAAjAADw8wAA8PMAAAAkAADw/gAA8P4AAAAlAADxEgAA8RIAAAAmAADxGAAA8RgAAAAnAADxPgAA8T4AAAAoAADxQQAA8UEAAAApAADxRAAA8UQAAAAqAADxZAAA8WQAAAArAADx5QAA8eUAAAAsAADyNAAA8jQAAAAtAAEAAP/2AtQCjQAkAB5AGyIZEAcEAAIBRwMBAgACbwEBAABmFBwUFAQFGCslFA8BBiIvAQcGIi8BJjQ/AScmND8BNjIfATc2Mh8BFhQPARcWAtQPTBAsEKSkECwQTBAQpKQQEEwQLBCkpBAsEEwPD6SkD3cWEEwPD6WlDw9MECwQpKQQLBBMEBCkpBAQTA8uD6SkDwAEAAD/uAOhAzUACAARACkAQABGQEM1AQcGCQACAgACRwAJBglvCAEGBwZvAAcDB28ABAACBFQFAQMBAQACAwBgAAQEAlgAAgQCTD08IzMjIjIlORgSCgUdKyU0Jg4CHgE2NzQmDgIeATY3FRQGIyEiJic1NDYXMx4BOwEyNjczMhYDBisBFRQGByMiJic1IyImPwE2Mh8BFgLKFB4UAhgaGI0UIBICFhwYRiAW/MsXHgEgFu4MNiOPIjYN7hYgtgkYjxQPjw8UAY8XExH6Ch4K+hIkDhYCEiASBBoMDhYCEiASBBqJsxYgIBazFiABHygoHx4BUhb6DxQBFg76LBH6Cgr6EQAAAAABAAD/0QOhA0cAHwAdQBoSDwoEAwUAAgFHAAIAAm8BAQAAZh0UFwMFFysBFA8BExUUDgEvAQcGIiY1NDcTJyY1NDclNzYyHwEFFgOhD8owDBUM+/oMFgwBMMsOHwEYfgsgDH0BGCAB8AwPxf7pDAsQAQeEhAcSCgQIARfFDwwVBSj+Fxf+KAUAAgAA/9EDoQNHAAkAKQAnQCQcGRQODQkIBwYFAwEMAAIBRwACAAJvAQEAAGYlJBcWEhADBRQrATcvAQ8BFwc3FxMUDwETFRQjIi8BBwYiJjU0NxMnJjU0NyU3NjIfAQUWAnuq62pp7Ksp09P+D8owFwoM+/oMFgwBMMsOHwEYfgsgDH0BGCABKaYi1dUiputvbwGyDA/F/ukMHAeEhAcSCgQIARfFDwwVBSj+Fxf+KAUAAAAAAgAA//8EMAKDACEAQwBCQD8iAQQGAUcDAQEHBgcBBm0JAQYEBwYEawgBAgAHAQIHYAAEAAAEVAAEBABYBQEABABMQkAWISUYIRYVKBMKBR0rJRQGJyEiJi8BLgEzESMiLgE/ATYyHwEWFAYHIxUhMh8BFiUUDwEGIi8BJjQ2OwE1ISIvASY0NjchMhYfAR4BFREzMhYCygoI/ekFBgIDAQIBaw8UAQizCyAMsgkWDmsBQQkFWQQBZQiyDCALswgWDmv+vgkFWQQKCAIYBAYCAwECaw4WEgcMAQIDBAEMAU8WGwrWDAzWChwUAdYGbAXiDQrWDQ3WChsW1gdrBQ0KAQIDBQIIA/6yFgAAAAUAAP/KA+gCuAAJABoAPgBEAFcAV0BUNBsCAARTBgICAFJDAgECUEIpJwgBBgYBBEcABQQFbwACAAEAAgFtAAEGAAEGawAGAwAGA2sAAwNuAAQAAARUAAQEAFgAAAQATExLEy4ZJBQdBwUaKyU3LgE3NDcGBxYBNCYHIgYVFBYyNjU0NjMyNjcUFQYCDwEGIyInJjU0Ny4BJyY0Nz4BMzIXNzYzMhYfARYHFhMUBgcTFhcUBwYHDgEjNz4BNyYnNx4BFxYBNiswOAEigFVeAWoQC0ZkEBYQRDALEMo76jscBQoHRAkZUIYyCwtW/JcyMh8FCgMOCyQLAQkVWEmdBPoLFidU3Hwpd8hFQV0jNWIgC3BPI2o9QzpBhJABZwsQAWRFCxAQCzBEEHUEAWn+WmkyCScGCgcqJHhNESoSg5gKNgkGBhQGAQX+/U6AGwEYGV4TEyQtYGpKCoRpZEA/JGI2EwAAAv///3EDoQMUAAgAIQBUQAofAQEADgEDAQJHS7AhUFhAFgAEAAABBABgAAEAAwIBA2AAAgINAkkbQB0AAgMCcAAEAAABBABgAAEDAwFUAAEBA1gAAwEDTFm3FyMUExIFBRkrATQuAQYUFj4BARQGIi8BBiMiLgI+BB4CFxQHFxYCg5LQkpLQkgEeLDoUv2R7UJJoQAI8bI6kjmw8AUW/FQGJZ5IClsqYBoz+mh0qFb9FPmqQoo5uOgRCZpZNe2S/FQAAAAIAAP+4A1oDEgAIAGoARUBCZVlMQQQABDsKAgEANCgbEAQDAQNHAAUEBW8GAQQABG8AAAEAbwABAwFvAAMCA28AAgJmXFtTUUlIKyoiIBMSBwUWKwE0JiIOARYyNiUVFAYPAQYHFhcWFAcOASciLwEGBwYHBisBIiY1JyYnBwYiJyYnJjQ3PgE3Ji8BLgEnNTQ2PwE2NyYnJjQ3PgEzMh8BNjc2NzY7ATIWHwEWFzc2MhcWFxYUBw4BBxYfAR4BAjtSeFICVnRWARwIB2gKCxMoBgUPUA0HB00ZGgkHBBB8CAwQGxdPBhAGRhYEBQgoCg8IZgcIAQoFaAgOFyUGBQ9QDQcITRgaCQgDEXwHDAEPHBdPBQ8HSBQEBAkoCg8IZgcKAWU7VFR2VFR4fAcMARAeFRsyBg4GFVABBTwNCEwcEAoHZwkMPAUGQB4FDgYMMg8cGw8BDAd8BwwBEBkaIC0HDAcUUAU8DQhMHBAKB2cJCzsFBUMcBQ4GDDIPHBoQAQwAAAACAAAAAANrAsoAJwBAAEJAPxQBAgEBRwAGAgUCBgVtAAUDAgUDawAEAwADBABtAAEAAgYBAmAAAwQAA1QAAwMAWAAAAwBMFiMZJSolJwcFGyslFBYPAQ4BByMiJjURNDY7ATIWFRcWDwEOAScjIgYHERQWFzMyHgIBFAcBBiImPQEjIiY9ATQ2NzM1NDYWFwEWAWUCAQIBCAiyQ15eQ7IICgEBAQIBCAiyJTQBNiS0BgIGAgIGC/7RCxwW+g4WFg76FhwLAS8LNQISBQ4JAgNeQwGIQ14KCAsJBg0HCAE0Jv54JTQBBAIIASwOC/7QChQPoRYO1g8UAaEOFgIJ/tAKAAAAAAEAAP/uA7YCMAAUABlAFg0BAAEBRwIBAQABbwAAAGYUFxIDBRcrCQEGIicBJjQ/ATYyFwkBNjIfARYUA6v+YgoeCv5iCwtdCh4KASgBKAscDFwLAZb+YwsLAZ0LHgpcCwv+2AEoCwtcCxwAAAH//v97A7gDZwAxAB9AHAABAAABVAABAQBYAgEAAQBMAQAqKQAxATEDBRQrFyInLgE3ATYXHgEXFgcBDgEnJjY3ATYWBwEGFxY3NjcBNiYnJgcBBh4CNwE2FgcBBvRmREgEVgHwUF4sRgwaUP4mKGAgHgYsAUwYNBr+tCwYDAwYFgHaMiA8Njb+EkIEZIZKAfAYNBr+EFKFSEbAXgHwUBoMRixgUP4mKAogGGQqAU4aNBj+tCwaCAIEFgHaMnYQDjL+EkyGYgRAAe4YLhr+EFIAAAAABP///7gELwMSAAgADwAfAC8AVUBSHRQCAQMPAQABDg0MCQQCABwVAgQCBEcAAgAEAAIEbQAGBwEDAQYDYAABAAACAQBgAAQFBQRUAAQEBVgABQQFTBEQLismIxkXEB8RHxMTEggFFysBFA4BJjQ2HgEBFSE1NxcBJSEiBgcRFBY3ITI2JxE0JhcRFAYHISImNxE0NjchMhYBZT5aPj5aPgI8/O6yWgEdAR78gwcKAQwGA30HDAEKUTQl/IMkNgE0JQN9JTQCGC0+AkJWQgQ6/vr6a7NZAR2hCgj9WgcMAQoIAqYIChL9WiU0ATYkAqYlNAE2AAv///9xBC8DEgAPAB8ALwA/AE8AXwBvAH8AjwCfAK8AxEAZkEACCQiIgGAgBAUEeDgCAwJQMAADAQAER0uwIVBYQDcAFRIMAggJFQhgEwEJEAEEBQkEYBENAgUOBgICAwUCYA8BAwoBAAEDAGALBwIBARRYABQUDRRJG0A+ABUSDAIICRUIYBMBCRABBAUJBGARDQIFDgYCAgMFAmAPAQMKAQABAwBgCwcCARQUAVQLBwIBARRYABQBFExZQCauq6ajnpuWlI6MhoR+fHZzbmtmZF5bVlROSzU1NSY1JjU1MxYFHSsXNTQmByMiBh0BFBY7ATI2JzU0JisBIgYdARQWNzMyNic1NCYnIyIGHQEUFhczMjYBETQmIyEiBhcRFBYzITI2ATU0JgcjIgYdARQWOwEyNgE1NCYHIyIGBxUUFjsBMjYDETQmByEiBhcRFBYXITI2FzU0JisBIgYHFRQWNzMyNjc1NCYnIyIGBxUUFhczMjY3NTQmByMiBgcVFBY7ATI2NxEUBiMhIiY3ETQ2NyEyFtYUD0gOFhYOSA4WARQPSA4WFg5IDhYBFA9IDhYWDkgOFgI7Fg7+Uw4WARQPAa0PFP3FFA9IDhYWDkgOFgMRFg5HDxQBFg5HDxTVFg7+Uw4WARQPAa0PFNcWDkcPFAEWDkcPFAEWDkcPFAEWDkcPFAEWDkcPFAEWDkcPFEg0JfyDJDYBNCUDfSU0JEgOFgEUD0gOFhbkSA4WFg5IDhYBFOZHDxQBFg5HDxQBFv5hAR4OFhYO/uIOFhYCkUcPFgEUEEcOFhb9i0gOFgEUD0gOFhYBuwEdDxYBFBD+4w8UARbJSA4WFg5IDhYBFOZHDxQBFg5HDxQBFuRHDxYBFBBHDhYWZ/0SJTQ0JQLuJTQBNgABAAD/xwJ0A0sAFAAXQBQJAQABAUcAAQABbwAAAGYcEgIFFisJAQYiLwEmNDcJASY0PwE2MhcBFhQCav5iCxwLXQsLASj+2AsLXQoeCgGeCgFw/mEKCl0LHAsBKQEoCxwLXQsL/mILHAAAAAABAAD/xwKYA0sAFAAXQBQBAQABAUcAAQABbwAAAGYXFwIFFisJAhYUDwEGIicBJjQ3ATYyHwEWFAKO/tcBKQoKXQscC/5iCwsBngoeCl0KArH+2P7XCh4KXQoKAZ8KHgoBngsLXQoeAAEAAAAAA7YCTQAUABlAFgUBAAIBRwACAAJvAQEAAGYXFBIDBRcrJQcGIicJAQYiLwEmNDcBNjIXARYUA6tcCx4K/tj+2AscC10LCwGeCxwLAZ4LclwKCgEp/tcKClwLHgoBngoK/mILHAAAAAQAAP91A8ADWQAqADQAPQBOALdAETY0AgQAHQ4CAQQCR0wBAQFGS7AaUFhAKQUBBAABAAQBbQMBAQYAAQZrAAYHAAYHawgBAAAMSAAHBwJYAAICDQJJG0uwJFBYQCYFAQQAAQAEAW0DAQEGAAEGawAGBwAGB2sABwACBwJcCAEAAAwASRtAJwgBAAQAbwUBBAEEbwMBAQYBbwAGBwZvAAcCAgdUAAcHAlgAAgcCTFlZQBcBAEpIREM6OTAvGxkWFRIQACoBKgkFFCsBIgYVFBcGBw4BFRQHBgcUFjsBFB4BMj4BNTMyNjUmJyY1NCYnJic2NTQmBQYHBhUzNDc2NyUHHgEHMzYnJgEyFhUUFjMyFhQGIyImNTQ2AfIWIAVHODM6OipNKh35JkFOQSb5HSpNKzo5NDdHBB/+tTseHUcWGDECOTAyLgFHAR0e/jcEBS8hBAUFBCg6BQNZHxYKDAsnJGk2tX1bQR0qJ0ImJkInKh1BW321NmkkJwsOCBYfLTZIRFFENjgtNDQtbkRQRUf9GAUEIS8FCAU6KAQFAAAAAgAAAAACgwMSAAcAHwAqQCcFAwIAAQIBAAJtAAICbgAEAQEEVAAEBAFYAAEEAUwjEyU2ExAGBRorEyE1NCYOARcFERQGByEiJicRNDYXMzU0NjIWBxUzMhazAR1UdlQBAdAgFv3pFx4BIBYRlMyWAhIXHgGsbDtUAlA9of6+Fh4BIBUBQhYgAWxmlJRmbB4AA//9/7gDWQMSAAwBvQH3AndLsAlQWEE8AL0AuwC4AJ8AlgCIAAYAAwAAAI8AAQACAAMA2gDTAG0AWQBRAEIAPgAzACAAGQAKAAcAAgGeAZgBlgGMAYsBegF1AWUBYwEDAOEA4AAMAAYABwFTAU0BKAADAAgABgH0AdsB0QHLAcABvgE4ATMACAABAAgABgBHG0uwClBYQUMAuwC4AJ8AiAAEAAUAAAC9AAEAAwAFAI8AAQACAAMA2gDTAG0AWQBRAEIAPgAzACAAGQAKAAcAAgGeAZgBlgGMAYsBegF1AWUBYwEDAOEA4AAMAAYABwFTAU0BKAADAAgABgH0AdsB0QHLAcABvgE4ATMACAABAAgABwBHAJYAAQAFAAEARhtBPAC9ALsAuACfAJYAiAAGAAMAAACPAAEAAgADANoA0wBtAFkAUQBCAD4AMwAgABkACgAHAAIBngGYAZYBjAGLAXoBdQFlAWMBAwDhAOAADAAGAAcBUwFNASgAAwAIAAYB9AHbAdEBywHAAb4BOAEzAAgAAQAIAAYAR1lZS7AJUFhANQACAwcDAgdtAAcGAwcGawAGCAMGCGsACAEDCAFrAAEBbgkBAAMDAFQJAQAAA1gFBAIDAANMG0uwClBYQDoEAQMFAgUDZQACBwUCB2sABwYFBwZrAAYIBQYIawAIAQUIAWsAAQFuCQEABQUAVAkBAAAFVgAFAAVKG0A1AAIDBwMCB20ABwYDBwZrAAYIAwYIawAIAQMIAWsAAQFuCQEAAwMAVAkBAAADWAUEAgMAA0xZWUEZAAEAAAHYAdYBuQG3AVcBVgDHAMUAtQC0ALEArgB5AHYABwAGAAAADAABAAwACgAFABQrATIeARQOASIuAj4BAQ4BBzI+ATU+ATc2FyY2PwE2PwEGJjUUBzQmBjUuBC8BJjQvAQcGFCoBFCIGIgc2JyYjNiYnMy4CJy4BBwYUHwEWBh4BBwYPAQYWFxYUBiIPAQYmJyYnJgcmJyYHMiYHPgEjNj8BNicWPwE2NzYyFjMWNCcyJyYnJgcGFyIPAQYvASYnIgc2JiM2JyYiDwEGHgEyFxYHIgYiBhYHLgEnFicjIgYiJyY3NBcnBgcyNj8BNhc3FyYHBgcWBycuASciBwYHHgIUNxYHMhcWFxYHJyYGFjMiDwEGHwEGFjcGHwMeAhcGFgciBjUeAhQWNzYnLgI1MzIfAQYeAjMeAQcyHgQfAxYyPwE2FhcWNyIfAR4BFR4BFzY1BhYzNjUGLwEmNCY2FzI2LgInBiYnFAYVIzY0PwE2LwEmByIHDgMmJy4BND8BNic2PwE2OwEyNDYmIxY2FxY3JyY3FjceAh8BFjY3FhceAT4BJjUnNS4BNjc0Nj8BNicyNycmIjc2Jz4BMxY2Jz4BNxY2Jj4BFTc2IxY3Nic2JiczMjU2JyYDNjcmIi8BNiYvASYvASYPASIPARUmJyIuAQ4BDwEmNiYGDwEGNgYVDgEVLgE3HgEXFgcGBwYXFAYWAa10xnJyxujIbgZ6vAETAggDAQIEAxEVEwoBDAIIBgMBBwYEBAoFBgQBCAECAQMDBAQEBAYBBgIICQUEBgIEAwEIDAEFHAQDAgIBCAEOAQIHCQMEBAEEAgMBBwoCBAUNAwMUDhMECAYBAgECBQkCARMJBgQCBQYKAwgEBwUCAwYJBAYBBQkEBQMDAgUEAQ4HCw8EEAMDAQgECAEIAwEIBAMCAgMEAgQSBQMMDAEDAwIMGRsDBgUFEwUDCwQNCwEEAgYECAQJBFEyBAUCBgUDARgKAQIHBQQDBAQEAQIBAQECCgcHEgQHCQQDCAQCDgEBAgIOAgQCAg8IAwQDAgMFAQQKCgEECAQFDAcCAwgDCQcWBgYFCAgQBBQKAQIEAgYDDgMEAQoFCBEKAgICAgEFAgQBCgIDDAMCCAECCAMBAwIHCwQBAgIIFAMICgECAQQCAwUCAQMCAQMBBBgDCQMBAQEDDQIOBAIDAQQDBQIGCAQCAgEIBAQHCAUHDAQEAgICBgEFBAMCAwUMBAISAQQCAgUOCQICCggFCQIGBgcFCQwKaXNQAQwBDQEEAxUBAwUCAwICAQUMCAMGBgYGAQEECAQKAQcGAgoCBAEMAQECAgQLDwECCQoBAxJ0xOrEdHTE6sR0/t0BCAIGBgEECAMFCwEMAQMCAgwBCgcCAwQCBAECBgwFBgMDAgQBAQMDBAIEAQMDAgIIBAIGBAEDBAEEBAYHAwgHCgcEBQYFDAMBAgQCAQMMCQ4DBAUHCAUDEQIDDggFDAMBAwkJBgQDBgEOBAoEAQIFAgIGCgQHBwcBCQUIBwgDAgcDAgQCBgIEBQoDAw4CBQICBQQHAgEKCA8CAwMHAwIOAwIDBAYEBgQEAQEtTwQBCAQDBAYPCgIGBAUEBQ4JFAsCAQYaAgEXBQQGAwUUAwMQBQIBBAgFCAQBCxgNBQwCAgQEDAgOBA4BCgsUBwgBBQMNAgECARIDCgQECQUGAgMKAwIDBQwCEAgSAwMEBAYCBAoHDgEFAgQBBAICEAUPBQIFAwILAggEBAICBBgOCQ4FCQEEBgECAwIBBAMGBwYFAg8KAQQBAgMBAgMIBRcEAggIAwUOAgoKBQECAwQLCQUCAgICBgIKBgoEBAQDAQQKBAYBBwIBBwYFBAIDAQUEAv4NFVUCAgUEBgIPAQECAQIBAQMCCgMGAgIFBgcDDgYCAQUEAggBAggCAgICBRwIEQkOCQwCBBAHAAIAAP+lA48DJAAMABcAIkAfFAEBAhEFAgABAkcAAgECbwABAAFvAAAAZhsWIgMFFyslFAYnIic+ASc0NjIWARYUBwEuAScBNjIB0K57UUREUgFYelgBniAh/sIUUjgBPiBe0XywASgnilI9WFgB9SBeIP7CN1QUAT4gAAAD//X/uAPzA1kADwAhADMAZEAMGxECAwIJAQIBAAJHS7AkUFhAHQACBQMFAgNtAAMAAAEDAGAAAQAEAQRcAAUFDAVJG0AiAAUCBW8AAgMCbwADAAABAwBgAAEEBAFUAAEBBFgABAEETFlACRc4JycmIwYFGislNTQmKwEiBh0BFBYXMzI2JxM0JyYrASIHBhUXFBY3MzI2AwEWBw4BByEiJicmNwE+ATIWAjsKB2wHCgoHbAcKAQoFBwd6BggFCQwHZwgMCAGsFBUJIhL8phIiCRUUAa0JIiYiWmoICgoIaggKAQzXAQEGBAYGBAj/BQgBBgIQ/O4jIxESARQQIyMDEhEUFAAAAAABAAAAAAMSAxIAIwApQCYABAMEbwABAAFwBQEDAAADVAUBAwMAWAIBAAMATCMzJSMzIwYFGisBFRQGJyMVFAYHIyImNzUjIiYnNTQ2NzM1NDY7ATIWFxUzMhYDEiAW6CAWaxYgAegXHgEgFugeF2sXHgHoFx4BvmsWIAHpFh4BIBXpHhdrFx4B6BYgIBboIAAC//3/uANfAxIABwAUACtAKAADAAABAwBgBAEBAgIBVAQBAQECWAACAQJMAAASEQwLAAcABxEFBRUrJREiDgIeAQEUDgEiLgI+ATIeAQGtU4xQAlSIAgFyxujIbgZ6vPS6fjUCYFKMpIxSATB1xHR0xOrEdHTEAAAFAAAAAAPkAxIABgAPADkAPgBIAQdAFUA+OxADAgEHAAQ0AQEAAkdBAQQBRkuwClBYQDAABwMEAwcEbQAABAEBAGUAAwAEAAMEYAgBAQAGBQEGXwAFAgIFVAAFBQJYAAIFAkwbS7ALUFhAKQAABAEBAGUHAQMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMG0uwGFBYQDAABwMEAwcEbQAABAEBAGUAAwAEAAMEYAgBAQAGBQEGXwAFAgIFVAAFBQJYAAIFAkwbQDEABwMEAwcEbQAABAEEAAFtAAMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMWVlZQBYAAERDPTwxLikmHhsWEwAGAAYUCQUVKyU3JwcVMxUBJg8BBhY/ATYTFRQGIyEiJjURNDY3ITIXHgEPAQYnJiMhIgYHERQWFyEyNj0BND8BNhYDFwEjNQEHJzc2Mh8BFhQB8EBVQDUBFQkJxAkSCcQJJF5D/jBDXl5DAdAjHgkDBxsICg0M/jAlNAE2JAHQJTQFJAgYN6H+iaECbzOhMxAsEFUQxEFVQR82AZIJCcQJEgnECf6+akNeXkMB0EJeAQ4EEwYcCAQDNCX+MCU0ATYkRgcFJAgIAY+g/omgAS40oTQPD1UQLAAEAAD/uANNAwYABgAUABkAJACGQBceAQIFHRYOBwQDAhkDAgMAAwEBAQAER0uwElBYQCcABQIFbwACAwJvAAMAA28AAAEBAGMGAQEEBAFSBgEBAQRXAAQBBEsbQCYABQIFbwACAwJvAAMAA28AAAEAbwYBAQQEAVIGAQEBBFcABAEES1lAEgAAISAYFxAPCQgABgAGFAcFFSszNycHFTMVATQjIgcBBhUUMzI3ATYnFwEjNQEUDwEnNzYyHwEWyzKDM0gBXwwFBP7RBA0FBAEvAx7o/jDoA00UXehdFDsWgxQzgzM8RwIGDAT+0gQGDAQBLgRx6P4v6QGaHRVd6VwVFYMWAAIAAP9xAoMDEgALAC4AY7YHAQIBAAFHS7AhUFhAGwAHCAYCAAEHAGAJBQIBBAECAwECYAADAw0DSRtAJAADAgNwAAcIBgIAAQcAYAkFAgECAgFUCQUCAQECWAQBAgECTFlADi0sEzMRFCIzFRUTCgUdKwE1NCYiBh0BFBYyNgUUBicjAw4BByMiJwMjIiYnNDYzESIuATY3ITIWFAYnETIWAQwKEAoKEAoBdxYO7x0BCgYBDwIr4Q8UAVg3HSoCLhsBZR0qKh03WAF3+ggKCgj6CAoKvQ4WAf7yBwgBDwEPFA9FbgEeKjoqASw4LAH+4m4AAAADAAD/fQOgAxIACAAUAC4AM0AwJgEEAygnEgMCBAABAQADRwADBANvAAQCBG8AAgACbwAAAQBvAAEBZhwjLRgSBQUZKzc0Jg4CHgE2JQEGIi8BJjQ3AR4BJRQHDgEnIiY0NjcyFhcWFA8BFRc2PwE2MhbWFB4UAhgaGAFm/oMVOhY7FRUBfBZUAZkNG4JPaJKSaCBGGQkJo2wCKkshDwokDhYCEiASBBr2/oMUFD0UOxYBfDdU3RYlS14BktCQAhQQBhIHXn08AhktFAoAAAAABQAA/7gEdwMSAAMABwANABEAFQBmQGMABQoFbw8BCgMKbwwBAwgDbw4BCAEIbwsBAQABbwkHAgMABgBvDQEGBAQGUg0BBgYEVgAEBgRKEhIODggIBAQAABIVEhUUEw4RDhEQDwgNCA0MCwoJBAcEBwYFAAMAAxEQBRUrAREjEQERIxEBFSERMxEBESMRJREjEQFljwFljgLK+4lHAsuPAWWPAWX+4gEeAR79xAI8/X1IA1r87gH0/lMBrdb9fQKDAAAAAAL//f9xA+sDWQAnAFAAsEAOJBYGAwECTEI0AwQDAkdLsCFQWEAmAAECAwIBA20HAQMEAgMEawACAgBYBgEAAAxIAAQEBVgABQUNBUkbS7AkUFhAIwABAgMCAQNtBwEDBAIDBGsABAAFBAVcAAICAFgGAQAADAJJG0ApAAECAwIBA20HAQMEAgMEawYBAAACAQACYAAEBQUEVAAEBAVYAAUEBUxZWUAXKSgBAEdFMS8oUClQFBIMCgAnAScIBRQrASIHBgcGBxQWHwEzMjU2NzY3NjMyFhcHBhYfARY+AS8BLgEPASYnJgEiFQYHBgcGIyInJic3NiYvASYOAR8BHgE/ARYXFjMyNzY3Njc0Ji8BAe6DcW1DRQUFBARUEwU1M1NXY0+ONDoJAgz3CxQKBDoCEglBRFpcATMTBTUzU1ZjUEhFNTsIAgv4CxQKBDoCEgpARFpdZoJxbkJFBQUEBANZQD5rboEICQIBEmJTUS8xPjg5CRMDMgMJFhDjCAsGPEYmKP4EEmJTUS8xIB44OQkTAzIDCRYQ4wgLBjxGJihAPmtugggIAgEAAAAAAv///2ID6gNZAB8AQQBJQAoEAQIAAUcxAQFES7AkUFhAEwACAAEAAgFtAAEBbgMBAAAMAEkbQA8DAQACAG8AAgECbwABAWZZQA0BACEgFBMAHwEfBAUUKwEiBwYHMTY3NhcWFxYXFgYHBhceATc+ATc2JicuAScmASIHBgcGBwYWFxYXFhcWNzY3MQYHBicmJyYnJjY3NiYnJgHyV1FURFZsamdqT0IhIQYlDhoQMxEDCgIjASUmkF5b/gUYDwQEBgEkAiQmSFt7d3l9YVZsamdrT0IhIAUlCAYOEgNZHR45RRUUHiBPQlZTs1EpGxABEQMPBlrDWV2QJiX+7hAEBggGWsNZXUhbJCIYGVFFFRQeIE9CVlOzURUhDhIAAAAAAgAAAAAD6ANZACcAPwB9QBMoAQEGEQECATcuAgQCIQEFBARHS7AkUFhAJAAEAgUCBAVtAAUDAgUDawABAAIEAQJgAAMAAAMAXAAGBgwGSRtALAAGAQZvAAQCBQIEBW0ABQMCBQNrAAEAAgQBAmAAAwAAA1QAAwMAWAAAAwBMWUAKOhslNTYlMwcFGysBFRQGIyEiJjURNDY3ITIWHQEUBiMhIgYHERQWFyEyNj0BNDY7ATIWExEUDgEvAQEGIi8BJjQ3AScmNDYzITIWAxJeQ/4wQ15eQwGJBwoKB/53JTQBNiQB0CU0CggkCArWFhwLYv6UBRAEQAYGAWxiCxYOAR0PFAFTskNeXkMB0EJeAQoIJAgKNCX+MCU0ATYksggKCgHa/uMPFAIMYv6UBgZABQ4GAWxiCxwWFgAAAAIAAP+4A1kDEgAYACgAMkAvEgkCAgABRwACAAEAAgFtAAQAAAIEAGAAAQMDAVQAAQEDWAADAQNMNTcUGTMFBRkrARE0JichIgYfAQEGFB8BFjI3ARcWMzI3NhMRFAYHISImNRE0NjchMhYCyhQP/vQYExJQ/tYLCzkLHAsBKlEKDwYIFY9eQ/3pQ15eQwIXQ14BUwEMDxQBLRBQ/tYLHgo5CgoBKlALAwoBNf3oQl4BYEECGEJeAWAAAAAAAwAAAAADWgLLAA8AHwAvADdANCgBBAUIAAIAAQJHAAUABAMFBGAAAwACAQMCYAABAAABVAABAQBYAAABAEwmNSY1JjMGBRorJRUUBgchIiYnNTQ2NyEyFgMVFAYnISImJzU0NhchMhYDFRQGIyEiJic1NDYXITIWA1kUEPzvDxQBFg4DEQ8WARQQ/O8PFAEWDgMRDxYBFBD87w8UARYOAxEPFmtHDxQBFg5HDxQBFgEQSA4WARQPSA4WARQBDkcOFhYORw8WARQAAAAAAv///7gD6QLKABkAOAAtQCoJAAICAwFHAAMCA28AAgECbwABAAABVAABAQBYAAABAEw3NCYkOjMEBRYrAREUBgchIiY3ERYXFhceAjczMj4BNzY3NjcUBgcGDwEOAicjIiYvAS4BLwEmJy4BJzQ2MyEyFgPoNCX8yiQ2ARkfykwgJkQbAhxCKB9ftyAYNinSNDUMIh4NAgweER4NIgaTYBIjPAEuKwM2JDYBzf5FJTQBNiQBuxsWiTcYGhwBGhwXRHwWvyxQHZIjJwkSDAEKChIIHANlQg4XUiQrOjQAAAACAAD/cQPoAsoAFwA9AGJADDQIAgEAJgsCAwICR0uwIVBYQBcABAUBAAEEAGAAAQACAwECYAADAw0DSRtAHgADAgNwAAQFAQABBABgAAECAgFUAAEBAlgAAgECTFlAEQEAOzokIh0bEhAAFwEXBgUUKwEiDgEHFBYfAQcGBzY/ARcWMzI+Ai4BARQOASMiJwYHBgcjIiYnNSY2Jj8BNj8BPgI/AS4BJzQ+ASAeAQH0csZ0AVBJMA8NGlVFGCAmInLGdAJ4wgGAhuaIJypukxskAwgOAgIEAgMMBA0UBxQQBw9YZAGG5gEQ5oYCg06ETD5yKRw1My4kPBUDBU6EmIRO/uJhpGAEYSYIBAwJAQIIBAMPBQ4WCBwcEyoyklRhpGBgpAAAAgAA/3EDxANaAAwANACeQAsaDQIBBgABAgACR0uwIVBYQCcAAQYDBgEDbQUBAwAGAwBrAAACBgACawAGBgxIAAICBFgABAQNBEkbS7AkUFhAJAABBgMGAQNtBQEDAAYDAGsAAAIGAAJrAAIABAIEXAAGBgwGSRtAJQAGAQZvAAEDAW8FAQMAA28AAAIAbwACBAQCVAACAgRYAAQCBExZWUAKHyISIyMTEgcFGysFNCMiJjc0IhUUFjcyJRQGKwEUBiImNSMiJjU+BDc0NjcmNTQ+ARYVFAceARcUHgMB/QkhMAESOigJAccqHfpUdlT6HSocLjAkEgKEaQUgLCAFaoIBFiIwMFkIMCEJCSk6AakdKjtUVDsqHRgyVF6ITVSSEAoLFx4CIhULChCSVE6GYFI0AAACAAD/uANZAxIAIwAzAEFAPg0BAAEfAQQDAkcCAQABAwEAA20FAQMEAQMEawAHAAEABwFgAAQGBgRUAAQEBlgABgQGTDU1IzMWIyQjCAUcKwE1NCYHIzU0JicjIgYHFSMiBgcVFBY3MxUUFjsBMjY3NTMyNhMRFAYHISImNRE0NjchMhYCyhQPsxYORw8UAbIPFAEWDrIWDkcPFAGzDhaOXkP96UNeXkMCF0NeAUFIDhYBsw8UARYOsxQPSA4WAbMOFhYOsxQBP/3oQl4BYEECGEJeAWAAAAABAAD/uAPoAzUAKwApQCYmAQQDAUcAAwQDbwAEAQRvAAECAW8AAgACbwAAAGYjFxM9FwUFGSslFAcOAgcGIiY1NDY3NjU0LgUrARUUBiInASY0NwE2MhYHFTMgFxYD6EcBCgQFBxEKAgEDFCI4PlZWN30UIAn+4wsLAR0LHBgCfQGOWh7oXZ8EEhAECgwIBRQDJh84WkAwHhIGjw4WCwEeCh4KAR4KFA+P4UsABf/9/7gDXwMSABMAHAAlADYAQwBCQD8dFAICAwFHAAkABgMJBmAFAQMEAQIBAwJgAAEAAAcBAGAABwgIB1QABwcIWAAIBwhMQUAXFxYTFBMZGRIKBR0rAQ4BLgEnJj4BFhceATI2Nz4BHgElFAYiJj4CFgUUBiIuAT4BFhc0LgIiDgIeAz4DNxQOASIuAj4BMh4BAnkVcI5yFAQOHBoEDkxeSg8EHBoQ/uYqOiwCKD4mASAqPCgCLDgujTpeho6IXDwCOGCEkoJiNklyxujIbgZ6vPS6fgEBQ1QCUEUOGgkMECw4OCwPDgoa5R4qKjwoAiwcHioqPCgCLKtJhGA4OGCEkoRePAQ0ZnxNdcR0dMTqxHR0xAAAAAEAAAAAAoMDWgAjAGZLsCRQWEAgAAQFAAUEAG0CBgIAAQUAAWsAAQFuAAUFA1gAAwMMBUkbQCUABAUABQQAbQIGAgABBQABawABAW4AAwUFA1QAAwMFWAAFAwVMWUATAQAgHxsYFBMQDgkGACMBIwcFFCsBMhYXERQGByEiJicRNDYXMzU0Nh4BBxQGKwEiJjU0JiIGFxUCTRceASAW/ekXHgEgFhGUzJYCFA8kDhZUdlQBAaweF/6+Fh4BIBUBQhYgAbNnlAKQaQ4WFg47VFQ7swAAAwAAAAADEgH0AA8AHwAvACJAHwUDAgEAAAFUBQMCAQEAWAQCAgABAEw1NTU1NTMGBRorExUUBicjIiYnNTQ2NzMyFgUVFAYnIyImNzU0NjczMhYFFRQGJyMiJj0BNDY3MzIW1h4XaxceASAWaxYgAR0gFmsWIAEeF2sXHgEfIBZrFiAgFmsXHgG+axYgAR4XaxceASAWaxYgAR4XaxceASAWaxYgAR4XaxceASAAAAAC//3/uANZAxIADAAaACZAIwMBAAIAbwACAQECVAACAgFYAAECAUwBABkYBwYADAEMBAUUKwEyHgEUDgEiLgI+AQE2NCclJgYVERQXFjI3Aa10xnJyxujIbgZ6vAFQEhL+0BEkEgkSCAMSdMTqxHR0xOrEdP40CioKsgsVFP6aFAsEBQADAAD/uAN9AxIACAAYAFUATkBLSgEIBx8bAgADAAEBADERAgIBBEcABwgHbwAIAwhvBgEDAANvAAABAG8ABAIEcAABAgIBVAABAQJYBQECAQJMLywVJD8mNRMSCQUdKzc0LgEOAR4BNhMRFAYHIyImJxE0NhczMhYFFAcWFRYHFgcGBxYHBgcjIi4BJyYnIiYnETQ+Ajc2Nz4CNz4DMzIeBAYXFA4BBw4CBzMyFo8WHRQBFh0UWhQQoA8UARYOoA8WApQfCQEZCQkJFgUgJEpIJVYyKkUTDxQBFBs6HCYSCg4GBQQGEBUPGSoYFAgGAgIMCAwBCAQDmytAaw8UARYdFAEWASz+mw8UARYOAWUOFgEUDzAjGRIqIh8jHxU+JysBEg4PGAEWDgFlDhYBQCMxEgoiFBgWGCIWDBIaGCASDRUsFhQEDA4GQAAAAAUAAP9xA+gDWQAQABQAJQAvADkA20AXMykCBwghAQUCHRUNDAQABQNHBAEFAUZLsCFQWEAtBgwDCwQBBwIHAQJtAAIFBwIFawAFAAcFAGsJAQcHCFgKAQgIDEgEAQAADQBJG0uwJFBYQCwGDAMLBAEHAgcBAm0AAgUHAgVrAAUABwUAawQBAABuCQEHBwhYCgEICAwHSRtAMgYMAwsEAQcCBwECbQACBQcCBWsABQAHBQBrBAEAAG4KAQgHBwhUCgEICAdWCQEHCAdKWVlAIBERAAA3NTIxLSsoJyQiHx4bGREUERQTEgAQAA83DQUVKwERFAYHERQGByEiJicREzYzIREjEQERFAYHISImJxEiJicRMzIXJRUjNTQ2OwEyFgUVIzU0NjsBMhYBiRYOFBD+4w8UAYsEDQGfjgI7Fg7+4w8UAQ8UAe0NBP4+xQoIoQgKAXfFCgihCAoCpv5UDxQB/r8PFAEWDgEdAegM/ngBiP4M/uMPFAEWDgFBFg4BrAytfX0ICgoIfX0ICgoAAAADAAD/uAR4AxMACAAsAE8Ad0B0LCUCCgcgHw4DAwIyEwIECANHAAEHAW8ABwoHbw4BAAoNCgANbQALDQINCwJtDAEKAA0LCg1gBgECBQEDCAIDYAAIBAQIVAAICARYCQEECARMAQBNS0pIRURBPzYzMS8pKCQiHBsXFRIQCgkFBAAIAQgPBRQrASImPgEeAgYFMzIWBxUUBisBFRQGByMiJj0BIyImJzU0NjczNTQ2FzMyFhcBFBY3MxUGIyEiJjU0PgUXMhceATI2NzYzMhcjIgYVAYlZfgJ6tngGhAHDxAcMAQoIxAwGawgKxQcKAQwGxQoIawcKAf5lKh2PJjn+GENSBAwSHiY6IQsLLFRkVCwLC0kwfR0qAWV+sIACfLR6SQwGawgKxQcKAQwGxQoIawcKAcQHDAEKCP6/HSwBhRxOQx44QjY4IhoCCiIiIiIKNiodAAAAAAEAAAABAABZIo1OXw889QALA+gAAAAA2WM3HwAAAADZYzcf//X/YgR4A2cAAAAIAAIAAAAAAAAAAQAAA1n/cQAABHb/9f/zBHgAAQAAAAAAAAAAAAAAAAAAAC4D6AAAAxEAAAOgAAADoAAAA6AAAAQvAAAD6AAAA6D//wNZAAADoAAAA+gAAAOr//4EL///BC///wLKAAACygAAA+gAAAPoAAACggAAA1n//QOgAAAD6P/1AxEAAANZ//0D6AAAA1kAAAKCAAADoAAABHYAAAPo//0D6f//A+gAAANZAAADWQAAA+j//wPoAAAD6AAAA1kAAAPoAAADWf/9AoIAAAMRAAADWf/9A6AAAAPoAAAEdgAAAAAAAABKAM4BEgFsAfICpAMGA8gESgSABOoFZAa2BuwHIAdWCCYIbgxyDLANNA18DbgOrg8wD6oQEhB0EUoR2hJ4EtYTPBOsFD4U2BVEFZoWJhaQFugXKhfQGJgZQwAAAAEAAAAuAfgACwAAAAAAAgAsADwAcwAAAKoLcAAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAIADUAAQAAAAAAAgAHAD0AAQAAAAAAAwAIAEQAAQAAAAAABAAIAEwAAQAAAAAABQALAFQAAQAAAAAABgAIAF8AAQAAAAAACgArAGcAAQAAAAAACwATAJIAAwABBAkAAABqAKUAAwABBAkAAQAQAQ8AAwABBAkAAgAOAR8AAwABBAkAAwAQAS0AAwABBAkABAAQAT0AAwABBAkABQAWAU0AAwABBAkABgAQAWMAAwABBAkACgBWAXMAAwABBAkACwAmAclDb3B5cmlnaHQgKEMpIDIwMTkgYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWZvbnRlbGxvUmVndWxhcmZvbnRlbGxvZm9udGVsbG9WZXJzaW9uIDEuMGZvbnRlbGxvR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADkAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGYAbwBuAHQAZQBsAGwAbwBSAGUAZwB1AGwAYQByAGYAbwBuAHQAZQBsAGwAbwBmAG8AbgB0AGUAbABsAG8AVgBlAHIAcwBpAG8AbgAgADEALgAwAGYAbwBuAHQAZQBsAGwAbwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8ABmNhbmNlbAZ1cGxvYWQEc3RhcgpzdGFyLWVtcHR5B3JldHdlZXQHZXllLW9mZgZzZWFyY2gDY29nBmxvZ291dAlkb3duLW9wZW4GYXR0YWNoB3BpY3R1cmUFdmlkZW8KcmlnaHQtb3BlbglsZWZ0LW9wZW4HdXAtb3Blbg5iZWxsLXJpbmdpbmctbwRsb2NrBWdsb2JlBWJydXNoCWF0dGVudGlvbgRwbHVzBmFkanVzdARlZGl0BnBlbmNpbANwaW4Gd3JlbmNoCWNoYXJ0LWJhcgVzcGluMwVzcGluNAhsaW5rLWV4dAxsaW5rLWV4dC1hbHQEbWVudQhtYWlsLWFsdA1jb21tZW50LWVtcHR5CGJlbGwtYWx0DHBsdXMtc3F1YXJlZAVyZXBseQVzbWlsZQ1sb2NrLW9wZW4tYWx0CGVsbGlwc2lzDHBsYXktY2lyY2xlZA10aHVtYnMtdXAtYWx0CmJpbm9jdWxhcnMJdXNlci1wbHVzAAAAAAEAAf//AA8AAAAAAAAAAAAAAAAAAAAAABgAGAAYABgDZ/9iA2f/YrAALCCwAFVYRVkgIEu4AA5RS7AGU1pYsDQbsChZYGYgilVYsAIlYbkIAAgAY2MjYhshIbAAWbAAQyNEsgABAENgQi2wASywIGBmLbACLCBkILDAULAEJlqyKAEKQ0VjRVJbWCEjIRuKWCCwUFBYIbBAWRsgsDhQWCGwOFlZILEBCkNFY0VhZLAoUFghsQEKQ0VjRSCwMFBYIbAwWRsgsMBQWCBmIIqKYSCwClBYYBsgsCBQWCGwCmAbILA2UFghsDZgG2BZWVkbsAErWVkjsABQWGVZWS2wAywgRSCwBCVhZCCwBUNQWLAFI0KwBiNCGyEhWbABYC2wBCwjISMhIGSxBWJCILAGI0KxAQpDRWOxAQpDsAFgRWOwAyohILAGQyCKIIqwASuxMAUlsAQmUVhgUBthUllYI1khILBAU1iwASsbIbBAWSOwAFBYZVktsAUssAdDK7IAAgBDYEItsAYssAcjQiMgsAAjQmGwAmJmsAFjsAFgsAUqLbAHLCAgRSCwC0NjuAQAYiCwAFBYsEBgWWawAWNgRLABYC2wCCyyBwsAQ0VCKiGyAAEAQ2BCLbAJLLAAQyNEsgABAENgQi2wCiwgIEUgsAErI7AAQ7AEJWAgRYojYSBkILAgUFghsAAbsDBQWLAgG7BAWVkjsABQWGVZsAMlI2FERLABYC2wCywgIEUgsAErI7AAQ7AEJWAgRYojYSBksCRQWLAAG7BAWSOwAFBYZVmwAyUjYUREsAFgLbAMLCCwACNCsgsKA0VYIRsjIVkqIS2wDSyxAgJFsGRhRC2wDiywAWAgILAMQ0qwAFBYILAMI0JZsA1DSrAAUlggsA0jQlktsA8sILAQYmawAWMguAQAY4ojYbAOQ2AgimAgsA4jQiMtsBAsS1RYsQRkRFkksA1lI3gtsBEsS1FYS1NYsQRkRFkbIVkksBNlI3gtsBIssQAPQ1VYsQ8PQ7ABYUKwDytZsABDsAIlQrEMAiVCsQ0CJUKwARYjILADJVBYsQEAQ2CwBCVCioogiiNhsA4qISOwAWEgiiNhsA4qIRuxAQBDYLACJUKwAiVhsA4qIVmwDENHsA1DR2CwAmIgsABQWLBAYFlmsAFjILALQ2O4BABiILAAUFiwQGBZZrABY2CxAAATI0SwAUOwAD6yAQEBQ2BCLbATLACxAAJFVFiwDyNCIEWwCyNCsAojsAFgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAULLEAEystsBUssQETKy2wFiyxAhMrLbAXLLEDEystsBgssQQTKy2wGSyxBRMrLbAaLLEGEystsBsssQcTKy2wHCyxCBMrLbAdLLEJEystsB4sALANK7EAAkVUWLAPI0IgRbALI0KwCiOwAWBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsB8ssQAeKy2wICyxAR4rLbAhLLECHistsCIssQMeKy2wIyyxBB4rLbAkLLEFHistsCUssQYeKy2wJiyxBx4rLbAnLLEIHistsCgssQkeKy2wKSwgPLABYC2wKiwgYLAQYCBDI7ABYEOwAiVhsAFgsCkqIS2wKyywKiuwKiotsCwsICBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4IyCKVVggRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOBshWS2wLSwAsQACRVRYsAEWsCwqsAEVMBsiWS2wLiwAsA0rsQACRVRYsAEWsCwqsAEVMBsiWS2wLywgNbABYC2wMCwAsAFFY7gEAGIgsABQWLBAYFlmsAFjsAErsAtDY7gEAGIgsABQWLBAYFlmsAFjsAErsAAWtAAAAAAARD4jOLEvARUqLbAxLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2E4LbAyLC4XPC2wMywgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhsAFDYzgtsDQssQIAFiUgLiBHsAAjQrACJUmKikcjRyNhIFhiGyFZsAEjQrIzAQEVFCotsDUssAAWsAQlsAQlRyNHI2GwCUMrZYouIyAgPIo4LbA2LLAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjILAIQyCKI0cjRyNhI0ZgsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhIyAgsAQmI0ZhOBsjsAhDRrACJbAIQ0cjRyNhYCCwBEOwAmIgsABQWLBAYFlmsAFjYCMgsAErI7AEQ2CwASuwBSVhsAUlsAJiILAAUFiwQGBZZrABY7AEJmEgsAQlYGQjsAMlYGRQWCEbIyFZIyAgsAQmI0ZhOFktsDcssAAWICAgsAUmIC5HI0cjYSM8OC2wOCywABYgsAgjQiAgIEYjR7ABKyNhOC2wOSywABawAyWwAiVHI0cjYbAAVFguIDwjIRuwAiWwAiVHI0cjYSCwBSWwBCVHI0cjYbAGJbAFJUmwAiVhuQgACABjYyMgWGIbIVljuAQAYiCwAFBYsEBgWWawAWNgIy4jICA8ijgjIVktsDossAAWILAIQyAuRyNHI2EgYLAgYGawAmIgsABQWLBAYFlmsAFjIyAgPIo4LbA7LCMgLkawAiVGUlggPFkusSsBFCstsDwsIyAuRrACJUZQWCA8WS6xKwEUKy2wPSwjIC5GsAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xKwEUKy2wPiywNSsjIC5GsAIlRlJYIDxZLrErARQrLbA/LLA2K4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrErARQrsARDLrArKy2wQCywABawBCWwBCYgLkcjRyNhsAlDKyMgPCAuIzixKwEUKy2wQSyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbErARQrLbBCLLA1Ky6xKwEUKy2wQyywNishIyAgPLAEI0IjOLErARQrsARDLrArKy2wRCywABUgR7AAI0KyAAEBFRQTLrAxKi2wRSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKyAAEAKrEABUKzCgIBCCqxAAVCsw4AAQgqsQAGQroCwAABAAkqsQAHQroAQAABAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbMMAgEMKrgB/4WwBI2xAgBEAAA=') format('truetype'); } /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ @@ -17,7 +17,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?49712213#fontello') format('svg'); + src: url('../font/fontello.svg?88512238#fontello') format('svg'); } } */ @@ -80,7 +80,6 @@ .icon-pin:before { content: '\e819'; } /* '' */ .icon-wrench:before { content: '\e81a'; } /* '' */ .icon-chart-bar:before { content: '\e81b'; } /* '' */ -.icon-zoom-in:before { content: '\e81c'; } /* '' */ .icon-spin3:before { content: '\e832'; } /* '' */ .icon-spin4:before { content: '\e834'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */ @@ -91,6 +90,7 @@ .icon-bell-alt:before { content: '\f0f3'; } /* '' */ .icon-plus-squared:before { content: '\f0fe'; } /* '' */ .icon-reply:before { content: '\f112'; } /* '' */ +.icon-smile:before { content: '\f118'; } /* '' */ .icon-lock-open-alt:before { content: '\f13e'; } /* '' */ .icon-ellipsis:before { content: '\f141'; } /* '' */ .icon-play-circled:before { content: '\f144'; } /* '' */ diff --git a/static/font/css/fontello-ie7-codes.css b/static/font/css/fontello-ie7-codes.css @@ -38,6 +38,7 @@ .icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f3;&nbsp;'); } .icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0fe;&nbsp;'); } .icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf112;&nbsp;'); } +.icon-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf118;&nbsp;'); } .icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf13e;&nbsp;'); } .icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf141;&nbsp;'); } .icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf144;&nbsp;'); } diff --git a/static/font/css/fontello-ie7.css b/static/font/css/fontello-ie7.css @@ -49,6 +49,7 @@ .icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f3;&nbsp;'); } .icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0fe;&nbsp;'); } .icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf112;&nbsp;'); } +.icon-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf118;&nbsp;'); } .icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf13e;&nbsp;'); } .icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf141;&nbsp;'); } .icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf144;&nbsp;'); } diff --git a/static/font/css/fontello.css b/static/font/css/fontello.css @@ -1,11 +1,11 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?4060331'); - src: url('../font/fontello.eot?4060331#iefix') format('embedded-opentype'), - url('../font/fontello.woff2?4060331') format('woff2'), - url('../font/fontello.woff?4060331') format('woff'), - url('../font/fontello.ttf?4060331') format('truetype'), - url('../font/fontello.svg?4060331#fontello') format('svg'); + src: url('../font/fontello.eot?94788965'); + src: url('../font/fontello.eot?94788965#iefix') format('embedded-opentype'), + url('../font/fontello.woff2?94788965') format('woff2'), + url('../font/fontello.woff?94788965') format('woff'), + url('../font/fontello.ttf?94788965') format('truetype'), + url('../font/fontello.svg?94788965#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -15,7 +15,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?4060331#fontello') format('svg'); + src: url('../font/fontello.svg?94788965#fontello') format('svg'); } } */ @@ -83,7 +83,6 @@ .icon-pin:before { content: '\e819'; } /* '' */ .icon-wrench:before { content: '\e81a'; } /* '' */ .icon-chart-bar:before { content: '\e81b'; } /* '' */ -.icon-zoom-in:before { content: '\e81c'; } /* '' */ .icon-spin3:before { content: '\e832'; } /* '' */ .icon-spin4:before { content: '\e834'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */ @@ -94,6 +93,7 @@ .icon-bell-alt:before { content: '\f0f3'; } /* '' */ .icon-plus-squared:before { content: '\f0fe'; } /* '' */ .icon-reply:before { content: '\f112'; } /* '' */ +.icon-smile:before { content: '\f118'; } /* '' */ .icon-lock-open-alt:before { content: '\f13e'; } /* '' */ .icon-ellipsis:before { content: '\f141'; } /* '' */ .icon-play-circled:before { content: '\f144'; } /* '' */ diff --git a/static/font/demo.html b/static/font/demo.html @@ -229,11 +229,11 @@ body { } @font-face { font-family: 'fontello'; - src: url('./font/fontello.eot?25455785'); - src: url('./font/fontello.eot?25455785#iefix') format('embedded-opentype'), - url('./font/fontello.woff?25455785') format('woff'), - url('./font/fontello.ttf?25455785') format('truetype'), - url('./font/fontello.svg?25455785#fontello') format('svg'); + src: url('./font/fontello.eot?31206390'); + src: url('./font/fontello.eot?31206390#iefix') format('embedded-opentype'), + url('./font/fontello.woff?31206390') format('woff'), + url('./font/fontello.ttf?31206390') format('truetype'), + url('./font/fontello.svg?31206390#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -340,21 +340,21 @@ body { <div class="the-icons span3" title="Code: 0xe81b"><i class="demo-icon icon-chart-bar">&#xe81b;</i> <span class="i-name">icon-chart-bar</span><span class="i-code">0xe81b</span></div> </div> <div class="row"> - <div class="the-icons span3" title="Code: 0xe81c"><i class="demo-icon icon-zoom-in">&#xe81c;</i> <span class="i-name">icon-zoom-in</span><span class="i-code">0xe81c</span></div> <div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div> <div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div> <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div> + <div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div> </div> <div class="row"> - <div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div> <div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div> <div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div> <div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div> + <div class="the-icons span3" title="Code: 0xf0f3"><i class="demo-icon icon-bell-alt">&#xf0f3;</i> <span class="i-name">icon-bell-alt</span><span class="i-code">0xf0f3</span></div> </div> <div class="row"> - <div class="the-icons span3" title="Code: 0xf0f3"><i class="demo-icon icon-bell-alt">&#xf0f3;</i> <span class="i-name">icon-bell-alt</span><span class="i-code">0xf0f3</span></div> <div class="the-icons span3" title="Code: 0xf0fe"><i class="demo-icon icon-plus-squared">&#xf0fe;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xf0fe</span></div> <div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div> + <div class="the-icons span3" title="Code: 0xf118"><i class="demo-icon icon-smile">&#xf118;</i> <span class="i-name">icon-smile</span><span class="i-code">0xf118</span></div> <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div> </div> <div class="row"> diff --git a/static/font/font/fontello.eot b/static/font/font/fontello.eot Binary files differ. diff --git a/static/font/font/fontello.svg b/static/font/font/fontello.svg @@ -84,6 +84,8 @@ <glyph glyph-name="reply" unicode="&#xf112;" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" /> +<glyph glyph-name="smile" unicode="&#xf118;" d="M633 257q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + <glyph glyph-name="lock-open-alt" unicode="&#xf13e;" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" /> <glyph glyph-name="ellipsis" unicode="&#xf141;" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" /> diff --git a/static/font/font/fontello.ttf b/static/font/font/fontello.ttf Binary files differ. diff --git a/static/font/font/fontello.woff b/static/font/font/fontello.woff Binary files differ. diff --git a/static/font/font/fontello.woff2 b/static/font/font/fontello.woff2 Binary files differ. diff --git a/test/unit/specs/components/emoji_input.spec.js b/test/unit/specs/components/emoji_input.spec.js @@ -0,0 +1,131 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils' +import EmojiInput from 'src/components/emoji_input/emoji_input.vue' + +const generateInput = (value, padEmoji = true) => { + const localVue = createLocalVue() + localVue.directive('click-outside', () => {}) + const wrapper = shallowMount(EmojiInput, { + propsData: { + suggest: () => [], + enableEmojiPicker: true, + value + }, + mocks: { + $store: { + state: { + config: { + padEmoji + } + } + } + }, + slots: { + default: '<input />' + }, + localVue + }) + return [wrapper, localVue] +} + +describe('EmojiInput', () => { + describe('insertion mechanism', () => { + it('inserts string at the end with trailing space', () => { + const initialString = 'Testing' + const [wrapper] = generateInput(initialString) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: initialString.length }) + wrapper.vm.insert({ insertion: '(test)', keepOpen: false }) + expect(wrapper.emitted().input[0][0]).to.eql('Testing (test) ') + }) + + it('inserts string at the end with trailing space (source has a trailing space)', () => { + const initialString = 'Testing ' + const [wrapper] = generateInput(initialString) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: initialString.length }) + wrapper.vm.insert({ insertion: '(test)', keepOpen: false }) + expect(wrapper.emitted().input[0][0]).to.eql('Testing (test) ') + }) + + it('inserts string at the begginning without leading space', () => { + const initialString = 'Testing' + const [wrapper] = generateInput(initialString) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: 0 }) + wrapper.vm.insert({ insertion: '(test)', keepOpen: false }) + expect(wrapper.emitted().input[0][0]).to.eql('(test) Testing') + }) + + it('inserts string between words without creating extra spaces', () => { + const initialString = 'Spurdo Sparde' + const [wrapper] = generateInput(initialString) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: 6 }) + wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false }) + expect(wrapper.emitted().input[0][0]).to.eql('Spurdo :ebin: Sparde') + }) + + it('inserts string between words without creating extra spaces (other caret)', () => { + const initialString = 'Spurdo Sparde' + const [wrapper] = generateInput(initialString) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: 7 }) + wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false }) + expect(wrapper.emitted().input[0][0]).to.eql('Spurdo :ebin: Sparde') + }) + + it('inserts string without any padding if padEmoji setting is set to false', () => { + const initialString = 'Eat some spam!' + const [wrapper] = generateInput(initialString, false) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: initialString.length, keepOpen: false }) + wrapper.vm.insert({ insertion: ':spam:' }) + expect(wrapper.emitted().input[0][0]).to.eql('Eat some spam!:spam:') + }) + + it('correctly sets caret after insertion at beginning', (done) => { + const initialString = '1234' + const [wrapper, vue] = generateInput(initialString) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: 0 }) + wrapper.vm.insert({ insertion: '1234', keepOpen: false }) + vue.nextTick(() => { + expect(wrapper.vm.caret).to.eql(5) + done() + }) + }) + + it('correctly sets caret after insertion at end', (done) => { + const initialString = '1234' + const [wrapper, vue] = generateInput(initialString) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: initialString.length }) + wrapper.vm.insert({ insertion: '1234', keepOpen: false }) + vue.nextTick(() => { + expect(wrapper.vm.caret).to.eql(10) + done() + }) + }) + + it('correctly sets caret after insertion if padEmoji setting is set to false', (done) => { + const initialString = '1234' + const [wrapper, vue] = generateInput(initialString, false) + const input = wrapper.find('input') + input.setValue(initialString) + wrapper.setData({ caret: initialString.length }) + wrapper.vm.insert({ insertion: '1234', keepOpen: false }) + vue.nextTick(() => { + expect(wrapper.vm.caret).to.eql(8) + done() + }) + }) + }) +})