aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md13
-rw-r--r--docs/USER_GUIDE.md9
-rw-r--r--docs/example_emoji.pngbin0 -> 491 bytes
-rw-r--r--index.html2
-rw-r--r--package.json1
-rw-r--r--src/App.js8
-rw-r--r--src/App.scss5
-rw-r--r--src/App.vue28
-rw-r--r--src/boot/after_store.js12
-rw-r--r--src/components/emoji_input/emoji_input.js (renamed from src/components/emoji-input/emoji-input.js)219
-rw-r--r--src/components/emoji_input/emoji_input.vue (renamed from src/components/emoji-input/emoji-input.vue)55
-rw-r--r--src/components/emoji_input/suggestor.js (renamed from src/components/emoji-input/suggestor.js)0
-rw-r--r--src/components/emoji_picker/emoji_picker.js115
-rw-r--r--src/components/emoji_picker/emoji_picker.scss165
-rw-r--r--src/components/emoji_picker/emoji_picker.vue110
-rw-r--r--src/components/extra_buttons/extra_buttons.vue4
-rw-r--r--src/components/media_modal/media_modal.vue5
-rw-r--r--src/components/media_upload/media_upload.vue16
-rw-r--r--src/components/mobile_post_status_button/mobile_post_status_button.js (renamed from src/components/mobile_post_status_modal/mobile_post_status_modal.js)25
-rw-r--r--src/components/mobile_post_status_button/mobile_post_status_button.vue (renamed from src/components/mobile_post_status_modal/mobile_post_status_modal.vue)38
-rw-r--r--src/components/notification/notification.js14
-rw-r--r--src/components/notification/notification.vue202
-rw-r--r--src/components/notifications/notifications.scss5
-rw-r--r--src/components/post_status_form/post_status_form.js119
-rw-r--r--src/components/post_status_form/post_status_form.vue80
-rw-r--r--src/components/post_status_modal/post_status_modal.js32
-rw-r--r--src/components/post_status_modal/post_status_modal.vue43
-rw-r--r--src/components/settings/settings.js4
-rw-r--r--src/components/settings/settings.vue8
-rw-r--r--src/components/status/status.vue18
-rw-r--r--src/components/sticker_picker/sticker_picker.js4
-rw-r--r--src/components/sticker_picker/sticker_picker.vue72
-rw-r--r--src/components/tab_switcher/tab_switcher.js26
-rw-r--r--src/components/tab_switcher/tab_switcher.scss11
-rw-r--r--src/components/user_card/user_card.js14
-rw-r--r--src/components/user_card/user_card.vue42
-rw-r--r--src/components/user_panel/user_panel.vue2
-rw-r--r--src/components/user_settings/user_settings.js4
-rw-r--r--src/components/user_settings/user_settings.vue2
-rw-r--r--src/directives/body_scroll_lock.js69
-rw-r--r--src/i18n/en.json12
-rw-r--r--src/i18n/es.json15
-rw-r--r--src/i18n/eu.json45
-rw-r--r--src/i18n/oc.json73
-rw-r--r--src/main.js6
-rw-r--r--src/modules/config.js1
-rw-r--r--src/modules/postStatus.js25
-rw-r--r--src/modules/statuses.js12
-rw-r--r--src/services/notifications_fetcher/notifications_fetcher.service.js5
-rw-r--r--src/services/offset_finder/offset_finder.service.js31
-rw-r--r--[-rwxr-xr-x]static/font/LICENSE.txt0
-rw-r--r--[-rwxr-xr-x]static/font/README.txt0
-rw-r--r--[-rwxr-xr-x]static/font/config.json6
-rw-r--r--[-rwxr-xr-x]static/font/css/animation.css0
-rw-r--r--[-rwxr-xr-x]static/font/css/fontello-codes.css1
-rw-r--r--[-rwxr-xr-x]static/font/css/fontello-embedded.css14
-rw-r--r--[-rwxr-xr-x]static/font/css/fontello-ie7-codes.css1
-rw-r--r--[-rwxr-xr-x]static/font/css/fontello-ie7.css1
-rw-r--r--[-rwxr-xr-x]static/font/css/fontello.css16
-rw-r--r--[-rwxr-xr-x]static/font/demo.html16
-rw-r--r--[-rwxr-xr-x]static/font/font/fontello.eotbin19452 -> 19376 bytes
-rw-r--r--[-rwxr-xr-x]static/font/font/fontello.svg2
-rw-r--r--[-rwxr-xr-x]static/font/font/fontello.ttfbin19284 -> 19208 bytes
-rw-r--r--[-rwxr-xr-x]static/font/font/fontello.woffbin11776 -> 11808 bytes
-rw-r--r--[-rwxr-xr-x]static/font/font/fontello.woff2bin9980 -> 10044 bytes
-rw-r--r--test/unit/specs/components/emoji_input.spec.js131
-rw-r--r--yarn.lock5
67 files changed, 1621 insertions, 398 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..ff4c2fd1
--- /dev/null
+++ 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
index cb95a244..18b6c560 100644
--- 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
new file mode 100644
index 00000000..0a22a256
--- /dev/null
+++ b/docs/example_emoji.png
Binary files differ
diff --git a/index.html b/index.html
index a8681c8b..fd4e795e 100644
--- a/index.html
+++ b/index.html
@@ -9,7 +9,7 @@
<link rel="stylesheet" href="/static/font/css/fontello.css">
<link rel="stylesheet" href="/static/font/css/animation.css">
</head>
- <body>
+ <body class="hidden">
<noscript>To use Pleroma, please enable JavaScript.</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
diff --git a/package.json b/package.json
index 0f6ae9c8..f039d412 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@chenfengyuan/vue-qrcode": "^1.0.0",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-lodash": "^3.2.11",
+ "body-scroll-lock": "^2.6.4",
"chromatism": "^3.0.0",
"cropperjs": "^1.4.3",
"diff": "^3.0.1",
diff --git a/src/App.js b/src/App.js
index e9cd5917..fe63b54c 100644
--- a/src/App.js
+++ b/src/App.js
@@ -8,9 +8,10 @@ import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_pan
import ChatPanel from './components/chat_panel/chat_panel.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
-import MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue'
+import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
+import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import { windowWidth } from './services/window_utils/window_utils'
export default {
@@ -26,9 +27,10 @@ export default {
ChatPanel,
MediaModal,
SideDrawer,
- MobilePostStatusModal,
+ MobilePostStatusButton,
MobileNav,
- UserReportingModal
+ UserReportingModal,
+ PostStatusModal
},
data: () => ({
mobileActivePanel: 'timeline',
diff --git a/src/App.scss b/src/App.scss
index ea7b54e8..2190f91a 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -10,7 +10,8 @@
position: fixed;
z-index: -1;
height: 100%;
- width: 100%;
+ left: 0;
+ right: -20px;
background-size: cover;
background-repeat: no-repeat;
background-position: 0 50%;
@@ -347,6 +348,7 @@ i[class*=icon-] {
align-items: center;
position: fixed;
height: 50px;
+ box-sizing: border-box;
.logo {
display: flex;
@@ -386,6 +388,7 @@ i[class*=icon-] {
}
.inner-nav {
+ position: relative;
margin: auto;
box-sizing: border-box;
padding-left: 10px;
diff --git a/src/App.vue b/src/App.vue
index 719e00a4..8d7f6c79 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -4,6 +4,7 @@
:style="bgAppStyle"
>
<div
+ id="app_bg_wrapper"
class="app-bg-wrapper"
:style="bgStyle"
/>
@@ -14,20 +15,20 @@
class="nav-bar container"
@click="scrollToTop()"
>
- <div
- class="logo"
- :style="logoBgStyle"
- >
+ <div class="inner-nav">
<div
- class="mask"
- :style="logoMaskStyle"
- />
- <img
- :src="logo"
- :style="logoStyle"
+ class="logo"
+ :style="logoBgStyle"
>
- </div>
- <div class="inner-nav">
+ <div
+ class="mask"
+ :style="logoMaskStyle"
+ />
+ <img
+ :src="logo"
+ :style="logoStyle"
+ >
+ </div>
<div class="item">
<router-link
class="site-name"
@@ -107,8 +108,9 @@
:floating="true"
class="floating-chat mobile-hidden"
/>
- <MobilePostStatusModal />
+ <MobilePostStatusButton />
<UserReportingModal />
+ <PostStatusModal />
<portal-target name="modal" />
</div>
</template>
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 5cb2acba..490ac4d0 100644
--- 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
index fab64a69..a586b819 100644
--- a/src/components/emoji-input/emoji-input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -1,5 +1,7 @@
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
@@ -52,6 +54,31 @@ const EmojiInput = {
*/
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 () {
@@ -60,10 +87,20 @@ const EmojiInput = {
highlighted: 0,
caret: 0,
focused: false,
- blurTimeout: null
+ 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 [] }
@@ -79,8 +116,12 @@ const EmojiInput = {
highlighted: index === this.highlighted
}))
},
- showPopup () {
- return this.focused && this.suggestions && this.suggestions.length > 0
+ showSuggestions () {
+ return this.focused &&
+ this.suggestions &&
+ this.suggestions.length > 0 &&
+ !this.showPicker &&
+ !this.temporarilyHideSuggestions
},
textAtCaret () {
return (this.wordAtCaret || {}).word || ''
@@ -104,6 +145,7 @@ const EmojiInput = {
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)
},
@@ -115,16 +157,80 @@ const EmojiInput = {
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 }
@@ -148,7 +254,7 @@ const EmojiInput = {
},
cycleBackward (e) {
const len = this.suggestions.length || 0
- if (len > 0) {
+ if (len > 1) {
this.highlighted -= 1
if (this.highlighted < 0) {
this.highlighted = this.suggestions.length - 1
@@ -160,7 +266,7 @@ const EmojiInput = {
},
cycleForward (e) {
const len = this.suggestions.length || 0
- if (len > 0) {
+ if (len > 1) {
this.highlighted += 1
if (this.highlighted >= len) {
this.highlighted = 0
@@ -170,6 +276,37 @@ const EmojiInput = {
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()
},
@@ -191,50 +328,93 @@ const EmojiInput = {
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) {
- this.setCaret(e)
- this.resize()
-
const { ctrlKey, shiftKey, key } = e
- if (key === 'Tab') {
- if (shiftKey) {
+ // 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 {
+ } else if (key === 'ArrowDown') {
this.cycleForward(e)
}
+ if (key === 'Enter') {
+ if (!ctrlKey) {
+ this.replaceText(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
},
@@ -243,6 +423,7 @@ const EmojiInput = {
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'
}
}
}
diff --git a/src/components/emoji-input/emoji-input.vue b/src/components/emoji_input/emoji_input.vue
index 48739ec8..13530e8b 100644
--- a/src/components/emoji-input/emoji-input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -1,10 +1,32 @@
<template>
- <div class="emoji-input">
+ <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: !showPopup }"
+ :class="{ hide: !showSuggestions }"
>
<div class="autocomplete-panel-body">
<div
@@ -31,7 +53,7 @@
</div>
</template>
-<script src="./emoji-input.js"></script>
+<script src="./emoji_input.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@@ -39,11 +61,36 @@
.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: 9;
+ z-index: 20;
margin-top: 2px;
&.hide {
diff --git a/src/components/emoji-input/suggestor.js b/src/components/emoji_input/suggestor.js
index aec5c39d..aec5c39d 100644
--- 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
new file mode 100644
index 00000000..824412dd
--- /dev/null
+++ 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
new file mode 100644
index 00000000..b0ed00e9
--- /dev/null
+++ 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
new file mode 100644
index 00000000..42f20130
--- /dev/null
+++ 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/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index ed0f3aa4..6781a4f8 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -10,14 +10,14 @@
<div slot="popover">
<div class="dropdown-menu">
<button
- v-if="canMute && !status.muted"
+ v-if="canMute && !status.thread_muted"
class="dropdown-item dropdown-item-icon"
@click.prevent="muteConversation"
>
<i class="icon-eye-off" /><span>{{ $t("status.mute_conversation") }}</span>
</button>
<button
- v-if="canMute && status.muted"
+ v-if="canMute && status.thread_muted"
class="dropdown-item dropdown-item-icon"
@click.prevent="unmuteConversation"
>
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index ab5a36a5..06ced5a1 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -1,6 +1,7 @@
<template>
<div
v-if="showing"
+ v-body-scroll-lock="showing"
class="modal-view media-modal-view"
@click.prevent="hide"
>
@@ -43,6 +44,10 @@
.media-modal-view {
z-index: 1001;
+ body:not(.scroll-locked) & {
+ display: none;
+ }
+
&:hover {
.modal-view-button-arrow {
opacity: 0.75;
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
index ac32ae83..1dda7bc1 100644
--- 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/mobile_post_status_modal/mobile_post_status_modal.js b/src/components/mobile_post_status_button/mobile_post_status_button.js
index 3cec23c6..3e77148a 100644
--- a/src/components/mobile_post_status_modal/mobile_post_status_modal.js
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.js
@@ -1,14 +1,9 @@
-import PostStatusForm from '../post_status_form/post_status_form.vue'
import { debounce } from 'lodash'
-const MobilePostStatusModal = {
- components: {
- PostStatusForm
- },
+const MobilePostStatusButton = {
data () {
return {
hidden: false,
- postFormOpen: false,
scrollingDown: false,
inputActive: false,
oldScrollPos: 0,
@@ -28,8 +23,8 @@ const MobilePostStatusModal = {
window.removeEventListener('resize', this.handleOSK)
},
computed: {
- currentUser () {
- return this.$store.state.users.currentUser
+ isLoggedIn () {
+ return !!this.$store.state.users.currentUser
},
isHidden () {
return this.autohideFloatingPostButton && (this.hidden || this.inputActive)
@@ -57,17 +52,7 @@ const MobilePostStatusModal = {
window.removeEventListener('scroll', this.handleScrollEnd)
},
openPostForm () {
- this.postFormOpen = true
- this.hidden = true
-
- const el = this.$el.querySelector('textarea')
- this.$nextTick(function () {
- el.focus()
- })
- },
- closePostForm () {
- this.postFormOpen = false
- this.hidden = false
+ this.$store.dispatch('openPostStatusModal')
},
handleOSK () {
// This is a big hack: we're guessing from changed window sizes if the
@@ -105,4 +90,4 @@ const MobilePostStatusModal = {
}
}
-export default MobilePostStatusModal
+export default MobilePostStatusButton
diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue b/src/components/mobile_post_status_button/mobile_post_status_button.vue
index b6d7d3ba..9cf45de3 100644
--- a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.vue
@@ -1,23 +1,5 @@
<template>
- <div v-if="currentUser">
- <div
- v-show="postFormOpen"
- class="post-form-modal-view modal-view"
- @click="closePostForm"
- >
- <div
- class="post-form-modal-panel panel"
- @click.stop=""
- >
- <div class="panel-heading">
- {{ $t('post_status.new_status') }}
- </div>
- <PostStatusForm
- class="panel-body"
- @posted="closePostForm"
- />
- </div>
- </div>
+ <div v-if="isLoggedIn">
<button
class="new-status-button"
:class="{ 'hidden': isHidden }"
@@ -28,27 +10,11 @@
</div>
</template>
-<script src="./mobile_post_status_modal.js"></script>
+<script src="./mobile_post_status_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';
-.post-form-modal-view {
- align-items: flex-start;
-}
-
-.post-form-modal-panel {
- flex-shrink: 0;
- margin-top: 25%;
- margin-bottom: 2em;
- width: 100%;
- max-width: 700px;
-
- @media (orientation: landscape) {
- margin-top: 8%;
- }
-}
-
.new-status-button {
width: 5em;
height: 5em;
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 896c6d52..8e817f3b 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -9,7 +9,8 @@ const Notification = {
data () {
return {
userExpanded: false,
- betterShadow: this.$store.state.interface.browserSupport.cssFilter
+ betterShadow: this.$store.state.interface.browserSupport.cssFilter,
+ unmuted: false
}
},
props: [ 'notification' ],
@@ -23,11 +24,14 @@ const Notification = {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
- userProfileLink (user) {
+ generateUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
getUser (notification) {
return this.$store.state.users.usersObject[notification.from_profile.id]
+ },
+ toggleMute () {
+ this.unmuted = !this.unmuted
}
},
computed: {
@@ -47,6 +51,12 @@ const Notification = {
return this.userInStore
}
return this.notification.from_profile
+ },
+ userProfileLink () {
+ return this.generateUserProfileLink(this.user)
+ },
+ needMute () {
+ return this.user.muted
}
}
}
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index bafcd026..1f192c77 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -4,104 +4,126 @@
:compact="true"
:statusoid="notification.status"
/>
- <div
- v-else
- class="non-mention"
- :class="[userClass, { highlighted: userStyle }]"
- :style="[ userStyle ]"
- >
- <a
- class="avatar-container"
- :href="notification.from_profile.statusnet_profile_url"
- @click.stop.prevent.capture="toggleUserExpanded"
+ <div v-else>
+ <div
+ v-if="needMute && !unmuted"
+ class="container muted"
>
- <UserAvatar
- :compact="true"
- :better-shadow="betterShadow"
- :user="notification.from_profile"
- />
- </a>
- <div class="notification-right">
- <UserCard
- v-if="userExpanded"
- :user="getUser(notification)"
- :rounded="true"
- :bordered="true"
- />
- <span class="notification-details">
- <div class="name-and-action">
- <!-- eslint-disable vue/no-v-html -->
- <span
- v-if="!!notification.from_profile.name_html"
- class="username"
- :title="'@'+notification.from_profile.screen_name"
- v-html="notification.from_profile.name_html"
- />
- <!-- eslint-enable vue/no-v-html -->
- <span
- v-else
- class="username"
- :title="'@'+notification.from_profile.screen_name"
- >{{ notification.from_profile.name }}</span>
- <span v-if="notification.type === 'like'">
- <i class="fa icon-star lit" />
- <small>{{ $t('notifications.favorited_you') }}</small>
- </span>
- <span v-if="notification.type === 'repeat'">
- <i
- class="fa icon-retweet lit"
- :title="$t('tool_tip.repeat')"
+ <small>
+ <router-link :to="userProfileLink">
+ {{ notification.from_profile.screen_name }}
+ </router-link>
+ </small>
+ <a
+ href="#"
+ class="unmute"
+ @click.prevent="toggleMute"
+ ><i class="button-icon icon-eye-off" /></a>
+ </div>
+ <div
+ v-else
+ class="non-mention"
+ :class="[userClass, { highlighted: userStyle }]"
+ :style="[ userStyle ]"
+ >
+ <a
+ class="avatar-container"
+ :href="notification.from_profile.statusnet_profile_url"
+ @click.stop.prevent.capture="toggleUserExpanded"
+ >
+ <UserAvatar
+ :compact="true"
+ :better-shadow="betterShadow"
+ :user="notification.from_profile"
+ />
+ </a>
+ <div class="notification-right">
+ <UserCard
+ v-if="userExpanded"
+ :user="getUser(notification)"
+ :rounded="true"
+ :bordered="true"
+ />
+ <span class="notification-details">
+ <div class="name-and-action">
+ <!-- eslint-disable vue/no-v-html -->
+ <span
+ v-if="!!notification.from_profile.name_html"
+ class="username"
+ :title="'@'+notification.from_profile.screen_name"
+ v-html="notification.from_profile.name_html"
/>
- <small>{{ $t('notifications.repeated_you') }}</small>
- </span>
- <span v-if="notification.type === 'follow'">
- <i class="fa icon-user-plus lit" />
- <small>{{ $t('notifications.followed_you') }}</small>
- </span>
- </div>
+ <!-- eslint-enable vue/no-v-html -->
+ <span
+ v-else
+ class="username"
+ :title="'@'+notification.from_profile.screen_name"
+ >{{ notification.from_profile.name }}</span>
+ <span v-if="notification.type === 'like'">
+ <i class="fa icon-star lit" />
+ <small>{{ $t('notifications.favorited_you') }}</small>
+ </span>
+ <span v-if="notification.type === 'repeat'">
+ <i
+ class="fa icon-retweet lit"
+ :title="$t('tool_tip.repeat')"
+ />
+ <small>{{ $t('notifications.repeated_you') }}</small>
+ </span>
+ <span v-if="notification.type === 'follow'">
+ <i class="fa icon-user-plus lit" />
+ <small>{{ $t('notifications.followed_you') }}</small>
+ </span>
+ </div>
+ <div
+ v-if="notification.type === 'follow'"
+ class="timeago"
+ >
+ <span class="faint">
+ <Timeago
+ :time="notification.created_at"
+ :auto-update="240"
+ />
+ </span>
+ </div>
+ <div
+ v-else
+ class="timeago"
+ >
+ <router-link
+ v-if="notification.status"
+ :to="{ name: 'conversation', params: { id: notification.status.id } }"
+ class="faint-link"
+ >
+ <Timeago
+ :time="notification.created_at"
+ :auto-update="240"
+ />
+ </router-link>
+ </div>
+ <a
+ v-if="needMute"
+ href="#"
+ @click.prevent="toggleMute"
+ ><i class="button-icon icon-eye-off" /></a>
+ </span>
<div
v-if="notification.type === 'follow'"
- class="timeago"
- >
- <span class="faint">
- <Timeago
- :time="notification.created_at"
- :auto-update="240"
- />
- </span>
- </div>
- <div
- v-else
- class="timeago"
+ class="follow-text"
>
- <router-link
- v-if="notification.status"
- :to="{ name: 'conversation', params: { id: notification.status.id } }"
- class="faint-link"
- >
- <Timeago
- :time="notification.created_at"
- :auto-update="240"
- />
+ <router-link :to="userProfileLink">
+ @{{ notification.from_profile.screen_name }}
</router-link>
</div>
- </span>
- <div
- v-if="notification.type === 'follow'"
- class="follow-text"
- >
- <router-link :to="userProfileLink(notification.from_profile)">
- @{{ notification.from_profile.screen_name }}
- </router-link>
+ <template v-else>
+ <status
+ class="faint"
+ :compact="true"
+ :statusoid="notification.action"
+ :no-heading="true"
+ />
+ </template>
</div>
- <template v-else>
- <status
- class="faint"
- :compact="true"
- :statusoid="notification.action"
- :no-heading="true"
- />
- </template>
</div>
</div>
</template>
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 622d12f4..71876b14 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -33,7 +33,6 @@
.notification {
box-sizing: border-box;
- display: flex;
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
@@ -47,6 +46,10 @@
}
}
+ .muted {
+ padding: .25em .6em;
+ }
+
.non-mention {
display: flex;
flex: 1;
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 40bbf6d4..9b2a9c90 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -1,14 +1,14 @@
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) => {
+const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
let allAttentions = [...attentions]
allAttentions.unshift(user)
@@ -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
index d29d47e4..4916d988 100644
--- 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/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js
new file mode 100644
index 00000000..1033ba11
--- /dev/null
+++ b/src/components/post_status_modal/post_status_modal.js
@@ -0,0 +1,32 @@
+import PostStatusForm from '../post_status_form/post_status_form.vue'
+
+const PostStatusModal = {
+ components: {
+ PostStatusForm
+ },
+ computed: {
+ isLoggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
+ isOpen () {
+ return this.isLoggedIn && this.$store.state.postStatus.modalActivated
+ },
+ params () {
+ return this.$store.state.postStatus.params || {}
+ }
+ },
+ watch: {
+ isOpen (val) {
+ if (val) {
+ this.$nextTick(() => this.$el.querySelector('textarea').focus())
+ }
+ }
+ },
+ methods: {
+ closeModal () {
+ this.$store.dispatch('closePostStatusModal')
+ }
+ }
+}
+
+export default PostStatusModal
diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue
new file mode 100644
index 00000000..3f8eec69
--- /dev/null
+++ b/src/components/post_status_modal/post_status_modal.vue
@@ -0,0 +1,43 @@
+<template>
+ <div
+ v-if="isOpen"
+ class="post-form-modal-view modal-view"
+ @click="closeModal"
+ >
+ <div
+ class="post-form-modal-panel panel"
+ @click.stop=""
+ >
+ <div class="panel-heading">
+ {{ $t('post_status.new_status') }}
+ </div>
+ <PostStatusForm
+ class="panel-body"
+ v-bind="params"
+ @posted="closeModal"
+ />
+ </div>
+ </div>
+</template>
+
+<script src="./post_status_modal.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.post-form-modal-view {
+ align-items: flex-start;
+}
+
+.post-form-modal-panel {
+ flex-shrink: 0;
+ margin-top: 25%;
+ margin-bottom: 2em;
+ width: 100%;
+ max-width: 700px;
+
+ @media (orientation: landscape) {
+ margin-top: 8%;
+ }
+}
+</style>
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index c4aa45b2..b6540d7e 100644
--- 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
index 744ec566..6d87a060 100644
--- 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
index 64218f6e..93f37a49 100644
--- 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
index a6dcded3..8daf3f07 100644
--- 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
index 938204c8..323855b9 100644
--- 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
index 08d5d08f..3ca316b9 100644
--- 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
index 4eeb42e0..3e5eacd5 100644
--- 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
index e3bd7697..9c931c01 100644
--- 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(', ')
}
@@ -168,6 +159,9 @@ export default {
}
this.$store.dispatch('setMedia', [attachment])
this.$store.dispatch('setCurrent', attachment)
+ },
+ mentionUser () {
+ this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
}
}
}
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 0b83cf16..5b6f66e7 100644
--- 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">
@@ -190,6 +194,15 @@
<div>
<button
+ class="btn btn-default btn-block"
+ @click="mentionUser"
+ >
+ {{ $t('user_card.mention') }}
+ </button>
+ </div>
+
+ <div>
+ <button
v-if="user.muted"
class="btn btn-default btn-block pressed"
@click="unmuteUser"
@@ -298,7 +311,7 @@
@import '../../_variables.scss';
.user-card {
- background-size: cover;
+ position: relative;
.panel-heading {
padding: .5em 0;
@@ -307,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
index c92630e3..e9f08015 100644
--- 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 v-if="user" />
+ <PostStatusForm />
</div>
</div>
<auth-form
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 108c82d2..21b01476 100644
--- 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
index e78f6e7c..a84e6f5c 100644
--- 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/directives/body_scroll_lock.js b/src/directives/body_scroll_lock.js
new file mode 100644
index 00000000..6ab20c3f
--- /dev/null
+++ b/src/directives/body_scroll_lock.js
@@ -0,0 +1,69 @@
+import * as bodyScrollLock from 'body-scroll-lock'
+
+let previousNavPaddingRight
+let previousAppBgWrapperRight
+
+const disableBodyScroll = (el) => {
+ const scrollBarGap = window.innerWidth - document.documentElement.clientWidth
+ bodyScrollLock.disableBodyScroll(el, {
+ reserveScrollBarGap: true
+ })
+ setTimeout(() => {
+ // If previousNavPaddingRight is already set, don't set it again.
+ if (previousNavPaddingRight === undefined) {
+ const navEl = document.getElementById('nav')
+ previousNavPaddingRight = window.getComputedStyle(navEl).getPropertyValue('padding-right')
+ navEl.style.paddingRight = previousNavPaddingRight ? `calc(${previousNavPaddingRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
+ }
+ // If previousAppBgWrapeprRight is already set, don't set it again.
+ if (previousAppBgWrapperRight === undefined) {
+ const appBgWrapperEl = document.getElementById('app_bg_wrapper')
+ previousAppBgWrapperRight = window.getComputedStyle(appBgWrapperEl).getPropertyValue('right')
+ appBgWrapperEl.style.right = previousAppBgWrapperRight ? `calc(${previousAppBgWrapperRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
+ }
+ document.body.classList.add('scroll-locked')
+ })
+}
+
+const enableBodyScroll = (el) => {
+ setTimeout(() => {
+ if (previousNavPaddingRight !== undefined) {
+ document.getElementById('nav').style.paddingRight = previousNavPaddingRight
+ // Restore previousNavPaddingRight to undefined so disableBodyScroll knows it can be set again.
+ previousNavPaddingRight = undefined
+ }
+ if (previousAppBgWrapperRight !== undefined) {
+ document.getElementById('app_bg_wrapper').style.right = previousAppBgWrapperRight
+ // Restore previousAppBgWrapperRight to undefined so disableBodyScroll knows it can be set again.
+ previousAppBgWrapperRight = undefined
+ }
+ document.body.classList.remove('scroll-locked')
+ })
+ bodyScrollLock.enableBodyScroll(el)
+}
+
+const directive = {
+ inserted: (el, binding) => {
+ if (binding.value) {
+ disableBodyScroll(el)
+ }
+ },
+ componentUpdated: (el, binding) => {
+ if (binding.oldValue === binding.value) {
+ return
+ }
+
+ if (binding.value) {
+ disableBodyScroll(el)
+ } else {
+ enableBodyScroll(el)
+ }
+ },
+ unbind: (el) => {
+ enableBodyScroll(el)
+ }
+}
+
+export default (Vue) => {
+ Vue.directive('body-scroll-lock', directive)
+}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index a63ee21d..25579e76 100644
--- 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",
@@ -531,6 +538,7 @@
"follows_you": "Follows you!",
"its_you": "It's you!",
"media": "Media",
+ "mention": "Mention",
"mute": "Mute",
"muted": "Muted",
"per_day": "per day",
diff --git a/src/i18n/es.json b/src/i18n/es.json
index 009599f5..91c7f383 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -508,7 +508,9 @@
"pinned": "Fijado",
"delete_confirm": "¿Realmente quieres borrar la publicación?",
"reply_to": "Respondiendo a",
- "replies_list": "Respuestas:"
+ "replies_list": "Respuestas:",
+ "mute_conversation": "Silenciar la conversación",
+ "unmute_conversation": "Mostrar la conversación"
},
"user_card": {
"approve": "Aprobar",
@@ -606,5 +608,16 @@
"person_talking": "{count} personas hablando",
"people_talking": "{count} gente hablando",
"no_results": "Sin resultados"
+ },
+ "password_reset": {
+ "forgot_password": "¿Contraseña olvidada?",
+ "password_reset": "Restablecer la contraseña",
+ "instruction": "Ingrese su dirección de correo electrónico o nombre de usuario. Le enviaremos un enlace para restablecer su contraseña.",
+ "placeholder": "Su correo electrónico o nombre de usuario",
+ "check_email": "Revise su correo electrónico para obtener un enlace para restablecer su contraseña.",
+ "return_home": "Volver a la página de inicio",
+ "not_found": "No pudimos encontrar ese correo electrónico o nombre de usuario.",
+ "too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.",
+ "password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia."
}
} \ No newline at end of file
diff --git a/src/i18n/eu.json b/src/i18n/eu.json
index 1efaa310..ad8f4c05 100644
--- a/src/i18n/eu.json
+++ b/src/i18n/eu.json
@@ -88,7 +88,7 @@
"followed_you": "Zu jarraitzen zaitu",
"load_older": "Kargatu jakinarazpen zaharragoak",
"notifications": "Jakinarazpenak",
- "read": "Irakurri!",
+ "read": "Irakurrita!",
"repeated_you": "zure mezua errepikatu du",
"no_more_notifications": "Ez dago jakinarazpen gehiago"
},
@@ -116,7 +116,7 @@
},
"post_status": {
"new_status": "Mezu berri bat idatzi",
- "account_not_locked_warning": "Zure kontua ez dago {0}. Edozeinek jarraitzen hastearekin, zure mezuak irakur dezake.",
+ "account_not_locked_warning": "Zure kontua ez dago {0}. Edozeinek jarraitzen hastearekin, zure mezuak irakur ditzake.",
"account_not_locked_warning_link": "Blokeatuta",
"attachments_sensitive": "Nabarmendu eranskinak hunkigarri gisa ",
"content_type": {
@@ -136,10 +136,10 @@
"unlisted": "Mezu hau ez da argitaratuko Denbora-lerro Publikoan ezta Ezagutzen den Sarean"
},
"scope": {
- "direct": "Zuzena - Bidali aipatutako erabiltzaileei besterik ez",
- "private": "Jarraitzaileentzako bakarrik- Bidali jarraitzaileentzat bakarrik",
- "public": "Publickoa - Bistaratu denbora-lerro publikoetan",
- "unlisted": "Zerrendatu gabea - ez bidali denbora-lerro publikoetan"
+ "direct": "Zuzena: Bidali aipatutako erabiltzaileei besterik ez",
+ "private": "Jarraitzaileentzako bakarrik: Bidali jarraitzaileentzat bakarrik",
+ "public": "Publikoa: Bistaratu denbora-lerro publikoetan",
+ "unlisted": "Zerrendatu gabea: ez bidali denbora-lerro publikoetara"
}
},
"registration": {
@@ -228,7 +228,7 @@
"avatar_size_instruction": "Avatar irudien gomendatutako gutxieneko tamaina 150x150 pixel dira.",
"export_theme": "Gorde aurre-ezarpena",
"filtering": "Iragazten",
- "filtering_explanation": "Hitz hauek dituzten muzu guztiak isilduak izango dira. Lerro bakoitzeko bat",
+ "filtering_explanation": "Hitz hauek dituzten mezu guztiak isilduak izango dira. Lerro bakoitzeko bat",
"follow_export": "Jarraitzen dituzunak esportatu",
"follow_export_button": "Esportatu zure jarraitzaileak csv fitxategi batean",
"follow_import": "Jarraitzen dituzunak inportatu",
@@ -276,7 +276,7 @@
"no_blocks": "Ez daude erabiltzaile blokeatutak",
"no_mutes": "Ez daude erabiltzaile mututuak",
"hide_follows_description": "Ez erakutsi nor jarraitzen ari naizen",
- "hide_followers_description": "Ez erakutsi nor ari de ni jarraitzen",
+ "hide_followers_description": "Ez erakutsi nor ari den ni jarraitzen",
"show_admin_badge": "Erakutsi Administratzaile etiketa nire profilan",
"show_moderator_badge": "Erakutsi Moderatzaile etiketa nire profilan",
"nsfw_clickthrough": "Gaitu klika hunkigarri eranskinak ezkutatzeko",
@@ -456,8 +456,8 @@
"time": {
"day": "{0} egun",
"days": "{0} egun",
- "day_short": "{0}d",
- "days_short": "{0}d",
+ "day_short": "{0}e",
+ "days_short": "{0}e",
"hour": "{0} ordu",
"hours": "{0} ordu",
"hour_short": "{0}o",
@@ -492,7 +492,7 @@
"conversation": "Elkarrizketa",
"error_fetching": "Errorea eguneraketak eskuratzen",
"load_older": "Kargatu mezu zaharragoak",
- "no_retweet_hint": "Mezu hau jarraitzailentzko bakarrik markatuta dago eta ezin da errepikatu",
+ "no_retweet_hint": "Mezu hau jarraitzailentzako bakarrik markatuta dago eta ezin da errepikatu",
"repeated": "Errepikatuta",
"show_new": "Berriena erakutsi",
"up_to_date": "Eguneratuta",
@@ -507,8 +507,10 @@
"unpin": "Aingura ezeztatu profilatik",
"pinned": "Ainguratuta",
"delete_confirm": "Mezu hau benetan ezabatu nahi duzu?",
- "reply_to": "Erantzun",
- "replies_list": "Erantzunak:"
+ "reply_to": "Erantzuten",
+ "replies_list": "Erantzunak:",
+ "mute_conversation": "Elkarrizketa isilarazi",
+ "unmute_conversation": "Elkarrizketa aktibatu"
},
"user_card": {
"approve": "Onartu",
@@ -581,7 +583,7 @@
},
"tool_tip": {
"media_upload": "Multimedia igo",
- "repeat": "Erreplikatu",
+ "repeat": "Errepikatu",
"reply": "Erantzun",
"favorite": "Gogokoa",
"user_settings": "Erabiltzaile ezarpenak"
@@ -601,10 +603,21 @@
}
},
"search": {
- "people": "Gendea",
+ "people": "Erabiltzaileak",
"hashtags": "Traolak",
"person_talking": "{count} pertsona hitzegiten",
- "people_talking": "{count} gende hitzegiten",
+ "people_talking": "{count} jende hitzegiten",
"no_results": "Emaitzarik ez"
+ },
+ "password_reset": {
+ "forgot_password": "Pasahitza ahaztua?",
+ "password_reset": "Pasahitza berrezarri",
+ "instruction": "Idatzi zure helbide elektronikoa edo erabiltzaile izena. Pasahitza berrezartzeko esteka bidaliko dizugu.",
+ "placeholder": "Zure e-posta edo erabiltzaile izena",
+ "check_email": "Begiratu zure posta elektronikoa pasahitza berrezarri ahal izateko.",
+ "return_home": "Itzuli hasierara",
+ "not_found": "Ezin izan dugu helbide elektroniko edo erabiltzaile hori aurkitu.",
+ "too_many_requests": "Saiakera gehiegi burutu ditzu, saiatu berriro geroxeago.",
+ "password_reset_disabled": "Pasahitza berrezartzea debekatuta dago. Mesedez, jarri harremanetan instantzia administratzailearekin."
}
} \ No newline at end of file
diff --git a/src/i18n/oc.json b/src/i18n/oc.json
index 6100a4d2..680ad6dd 100644
--- a/src/i18n/oc.json
+++ b/src/i18n/oc.json
@@ -5,7 +5,7 @@
"exporter": {
"export": "Exportar",
"processing": "Tractament, vos demandarem lèu de telecargar lo fichièr"
- },
+ },
"features_panel": {
"chat": "Chat",
"gopher": "Gopher",
@@ -30,12 +30,12 @@
"cancel": "Anullar"
},
"image_cropper": {
- "crop_picture": "Talhar l’imatge",
- "save": "Salvar",
- "save_without_cropping": "Salvar sens talhada",
- "cancel": "Anullar"
+ "crop_picture": "Talhar l’imatge",
+ "save": "Salvar",
+ "save_without_cropping": "Salvar sens talhada",
+ "cancel": "Anullar"
},
- "importer": {
+ "importer": {
"submit": "Mandar",
"success": "Corrèctament importat.",
"error": "Una error s’es producha pendent l’importacion d’aqueste fichièr."
@@ -65,6 +65,7 @@
"timeline": "Flux d’actualitat",
"twkn": "Lo malhum conegut",
"user_search": "Cèrca d’utilizaires",
+ "search": "Cercar",
"who_to_follow": "Qual seguir",
"preferences": "Preferéncias"
},
@@ -79,19 +80,27 @@
"no_more_notifications": "Pas mai de notificacions"
},
"polls": {
-"add_poll": "Ajustar un sondatge",
+ "add_poll": "Ajustar un sondatge",
"add_option": "Ajustar d’opcions",
- "option": "Opcion",
- "votes": "vòtes",
- "vote": "Votar",
- "type": "Tipe de sondatge",
- "single_choice": "Causida unica",
- "multiple_choices": "Causida multipla",
- "expiry": "Durada del sondatge",
- "expires_in": "Lo sondatge s’acabarà {0}",
- "expired": "Sondatge acabat {0}",
- "not_enough_options": "I a pas pro d’opcions"
- },
+ "option": "Opcion",
+ "votes": "vòtes",
+ "vote": "Votar",
+ "type": "Tipe de sondatge",
+ "single_choice": "Causida unica",
+ "multiple_choices": "Causida multipla",
+ "expiry": "Durada del sondatge",
+ "expires_in": "Lo sondatge s’acabarà {0}",
+ "expired": "Sondatge acabat {0}",
+ "not_enough_options": "I a pas pro d’opcions"
+ },
+ "stickers": {
+ "add_sticker": "Ajustar un pegasolet"
+ },
+ "interactions": {
+ "favs_repeats": "Repeticions e favorits",
+ "follows": "Nòus seguidors",
+ "load_older": "Cargar d’interaccions anterioras"
+ },
"post_status": {
"new_status": "Publicar d’estatuts novèls",
"account_not_locked_warning": "Vòstre compte es pas {0}. Qual que siá pòt vos seguir per veire vòstras publicacions destinadas pas qu’a vòstres seguidors.",
@@ -137,8 +146,8 @@
}
},
"selectable_list": {
- "select_all": "O seleccionar tot"
- },
+ "select_all": "O seleccionar tot"
+ },
"settings": {
"app_name": "Nom de l’aplicacion",
"attachmentRadius": "Pèças juntas",
@@ -216,7 +225,6 @@
"use_contain_fit": "Talhar pas las pèças juntas per las vinhetas",
"name": "Nom",
"name_bio": "Nom & Bio",
-
"new_password": "Nòu senhal",
"notification_visibility_follows": "Abonaments",
"notification_visibility_likes": "Aimar",
@@ -264,12 +272,12 @@
"subject_line_email": "Coma los corrièls : \"re: subjècte\"",
"subject_line_mastodon": "Coma mastodon : copiar tal coma es",
"subject_line_noop": "Copiar pas",
-"post_status_content_type": "Publicar lo tipe de contengut dels estatuts",
+ "post_status_content_type": "Publicar lo tipe de contengut dels estatuts",
"stop_gifs": "Lançar los GIFs al subrevòl",
"streaming": "Activar lo cargament automatic dels novèls estatus en anar amont",
"text": "Tèxte",
"theme": "Tèma",
- "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
+ "theme_help_v2_1": "Podètz tanben remplaçar la color d’unes compausants en clicant la case, utilizatz lo boton \"O escafar tot\" per escafar totes las subrecargadas.",
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
"theme_help": "Emplegatz los còdis de color hex (#rrggbb) per personalizar vòstre tèma de color.",
"tooltipRadius": "Astúcias/alèrtas",
@@ -280,14 +288,14 @@
"true": "òc"
},
"notifications": "Notificacions",
- "notification_setting": "Receber las notificacions de :",
+ "notification_setting": "Recebre las notificacions de :",
"notification_setting_follows": "Utilizaires que seguissètz",
"notification_setting_non_follows": "Utilizaires que seguissètz pas",
"notification_setting_followers": "Utilizaires que vos seguisson",
"notification_setting_non_followers": "Utilizaires que vos seguisson pas",
- "notification_mutes": "Per receber pas mai d’un utilizaire en particular, botatz-lo en silenci.",
+ "notification_mutes": "Per recebre pas mai d’un utilizaire en particular, botatz-lo en silenci.",
"notification_blocks": "Blocar un utilizaire arrèsta totas las notificacions tan coma quitar de los seguir.",
- "enable_web_push_notifications": "Activar las notificacions web push",
+ "enable_web_push_notifications": "Activar las notificacions web push",
"style": {
"switcher": {
"keep_color": "Gardar las colors",
@@ -442,7 +450,7 @@
"conversation": "Conversacion",
"error_fetching": "Error en cercant de mesas a jorn",
"load_older": "Ne veire mai",
- "no_retweet_hint": "Las publicacions marcadas pels seguidors solament o dirèctas se pòdon pas repetir",
+ "no_retweet_hint": "Las publicacions marcadas pels seguidors solament o dirèctas se pòdon pas repetir",
"repeated": "repetit",
"show_new": "Ne veire mai",
"up_to_date": "A jorn",
@@ -477,6 +485,8 @@
"per_day": "per jorn",
"remote_follow": "Seguir a distància",
"statuses": "Estatuts",
+ "subscribe": "S’abonar",
+ "unsubscribe": "Se desabonar",
"unblock": "Desblocar",
"unblock_progress": "Desblocatge...",
"block_progress": "Blocatge...",
@@ -501,7 +511,7 @@
"quarantine": "Defendre la federacion de las publicacions de l’utilizaire",
"delete_user": "Suprimir l’utilizaire",
"delete_user_confirmation": "Volètz vertadièrament far aquò ? Aquesta accion se pòt pas anullar."
- }
+ }
},
"user_profile": {
"timeline_title": "Flux utilizaire",
@@ -532,5 +542,12 @@
"GiB": "Gio",
"TiB": "Tio"
}
+ },
+ "search": {
+ "people": "Gent",
+ "hashtags": "Etiquetas",
+ "person_talking": "{count} persona ne parla",
+ "people_talking": "{count} personas ne parlan",
+ "no_results": "Cap de resultats"
}
} \ No newline at end of file
diff --git a/src/main.js b/src/main.js
index b3256e8e..7923ffe8 100644
--- a/src/main.js
+++ b/src/main.js
@@ -15,6 +15,7 @@ import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js'
import reportsModule from './modules/reports.js'
import pollsModule from './modules/polls.js'
+import postStatusModule from './modules/postStatus.js'
import VueI18n from 'vue-i18n'
@@ -26,6 +27,7 @@ import messages from './i18n/messages.js'
import VueChatScroll from 'vue-chat-scroll'
import VueClickOutside from 'v-click-outside'
import PortalVue from 'portal-vue'
+import VBodyScrollLock from './directives/body_scroll_lock'
import VTooltip from 'v-tooltip'
import afterStoreSetup from './boot/after_store.js'
@@ -38,6 +40,7 @@ Vue.use(VueI18n)
Vue.use(VueChatScroll)
Vue.use(VueClickOutside)
Vue.use(PortalVue)
+Vue.use(VBodyScrollLock)
Vue.use(VTooltip)
const i18n = new VueI18n({
@@ -76,7 +79,8 @@ const persistedStateOptions = {
mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule,
reports: reportsModule,
- polls: pollsModule
+ polls: pollsModule,
+ postStatus: postStatusModule
},
plugins: [persistedState, pushNotifications],
strict: false // Socket modifies itself, let's ignore this for now.
diff --git a/src/modules/config.js b/src/modules/config.js
index 2bfad8f6..cf04d14f 100644
--- 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/modules/postStatus.js b/src/modules/postStatus.js
new file mode 100644
index 00000000..638c1fb2
--- /dev/null
+++ b/src/modules/postStatus.js
@@ -0,0 +1,25 @@
+const postStatus = {
+ state: {
+ params: null,
+ modalActivated: false
+ },
+ mutations: {
+ openPostStatusModal (state, params) {
+ state.params = params
+ state.modalActivated = true
+ },
+ closePostStatusModal (state) {
+ state.modalActivated = false
+ }
+ },
+ actions: {
+ openPostStatusModal ({ commit }, params) {
+ commit('openPostStatusModal', params)
+ },
+ closePostStatusModal ({ commit }) {
+ commit('closePostStatusModal')
+ }
+ }
+}
+
+export default postStatus
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 4356d0a7..918065d2 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -426,9 +426,13 @@ export const mutations = {
newStatus.favoritedBy.push(user)
}
},
- setMuted (state, status) {
+ setMutedStatus (state, status) {
const newStatus = state.allStatusesObject[status.id]
- newStatus.muted = status.muted
+ newStatus.thread_muted = status.thread_muted
+
+ if (newStatus.thread_muted !== undefined) {
+ state.conversationsObject[newStatus.statusnet_conversation_id].forEach(status => { status.thread_muted = newStatus.thread_muted })
+ }
},
setRetweeted (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
@@ -566,11 +570,11 @@ const statuses = {
},
muteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.muteConversation(statusId)
- .then((status) => commit('setMuted', status))
+ .then((status) => commit('setMutedStatus', status))
},
unmuteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.unmuteConversation(statusId)
- .then((status) => commit('setMuted', status))
+ .then((status) => commit('setMutedStatus', status))
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index f9ec3f6e..b6c4cf80 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -10,6 +10,11 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
const args = { credentials }
const rootState = store.rootState || store.state
const timelineData = rootState.statuses.notifications
+ const hideMutedPosts = typeof rootState.config.hideMutedPosts === 'undefined'
+ ? rootState.instance.hideMutedPosts
+ : rootState.config.hideMutedPosts
+
+ args['withMuted'] = !hideMutedPosts
args['timeline'] = 'notifications'
if (older) {
diff --git a/src/services/offset_finder/offset_finder.service.js b/src/services/offset_finder/offset_finder.service.js
new file mode 100644
index 00000000..9034f8c8
--- /dev/null
+++ 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
index 95966f00..95966f00 100755..100644
--- a/static/font/LICENSE.txt
+++ b/static/font/LICENSE.txt
diff --git a/static/font/README.txt b/static/font/README.txt
index beaab336..beaab336 100755..100644
--- a/static/font/README.txt
+++ b/static/font/README.txt
diff --git a/static/font/config.json b/static/font/config.json
index 72a48a74..a8300b5f 100755..100644
--- 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
index ac5a9562..ac5a9562 100755..100644
--- 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
index 2083f618..c281baaf 100755..100644
--- 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
index ad4246e6..ca7dc40a 100755..100644
--- 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
index cbc41000..bdfe3efb 100755..100644
--- 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
index 1ef174bf..ee47dfd1 100755..100644
--- 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
index 84fd6802..653ada3c 100755..100644
--- 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
index 225e4ec5..1a28bc77 100755..100644
--- 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
index d08692e8..197a6042 100755..100644
--- 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
index fdd7caa7..7ffb321b 100755..100644
--- 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
index 6f5a81d7..6ec04a30 100755..100644
--- 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
index 79972a57..231c2c46 100755..100644
--- 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
index 94d79274..bf84b560 100755..100644
--- 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
new file mode 100644
index 00000000..368d623d
--- /dev/null
+++ 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()
+ })
+ })
+ })
+})
diff --git a/yarn.lock b/yarn.lock
index d6c8b4a6..4e0d9a22 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1196,6 +1196,11 @@ body-parser@1.18.3, body-parser@^1.16.1:
raw-body "2.3.3"
type-is "~1.6.16"
+body-scroll-lock@^2.6.4:
+ version "2.6.4"
+ resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-2.6.4.tgz#567abc60ef4d656a79156781771398ef40462e94"
+ integrity sha512-NP08WsovlmxEoZP9pdlqrE+AhNaivlTrz9a0FF37BQsnOrpN48eNqivKkE7SYpM9N+YIPjsdVzfLAUQDBm6OQw==
+
boolbase@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"