summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-11-13 00:12:55 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-11-13 00:12:55 +0900
commit0fbf56219f4067e0ba952ab8727cd76dc8919e16 (patch)
treed249e64bd93a131b27f4fee958247e5dafcfb01d /src/client
parent10.48.1 (diff)
downloadmisskey-0fbf56219f4067e0ba952ab8727cd76dc8919e16.tar.gz
misskey-0fbf56219f4067e0ba952ab8727cd76dc8919e16.tar.bz2
misskey-0fbf56219f4067e0ba952ab8727cd76dc8919e16.zip
[Client] Emoji picker
Closes #3130
Diffstat (limited to 'src/client')
-rw-r--r--src/client/app/common/views/components/emoji-picker.vue200
-rw-r--r--src/client/app/common/views/components/emoji.vue2
-rw-r--r--src/client/app/desktop/views/components/emoji-picker-dialog.vue84
-rw-r--r--src/client/app/desktop/views/components/post-form.vue87
-rw-r--r--src/client/theme/dark.json51
-rw-r--r--src/client/theme/light.json51
6 files changed, 350 insertions, 25 deletions
diff --git a/src/client/app/common/views/components/emoji-picker.vue b/src/client/app/common/views/components/emoji-picker.vue
new file mode 100644
index 0000000000..3d1dbd23ad
--- /dev/null
+++ b/src/client/app/common/views/components/emoji-picker.vue
@@ -0,0 +1,200 @@
+<template>
+<div class="prlncendiewqqkrevzeruhndoakghvtx">
+ <header>
+ <button v-for="category in categories"
+ :title="category.text"
+ @click="go(category.ref)"
+ :class="{ active: category.isActive }"
+ >
+ <fa :icon="category.icon" fixed-width/>
+ </button>
+ </header>
+ <div class="emojis" ref="emojis" @scroll.passive="onScroll">
+ <section v-for="category in categories" :ref="category.ref">
+ <header><fa :icon="category.icon" fixed-width/> {{ category.text }}</header>
+ <div v-if="category.name">
+ <button v-for="emoji in Object.entries(lib).filter(([k, v]) => v.category === category.name)"
+ :title="emoji[0]"
+ @click="chosen(emoji[1].char)"
+ >
+ <mk-emoji :emoji="emoji[1].char"/>
+ </button>
+ </div>
+ <div v-else>
+ <button v-for="emoji in customEmojis"
+ :title="emoji.name"
+ @click="chosen(`:${emoji.name}:`)"
+ >
+ <img :src="emoji.url" :alt="emoji.name"/>
+ </button>
+ </div>
+ </section>
+ </div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../../i18n';
+import { lib } from 'emojilib';
+
+export default Vue.extend({
+ i18n: i18n('common/views/components/emoji-picker.vue'),
+
+ data() {
+ return {
+ lib,
+ customEmojis: [],
+ categories: [{
+ ref: 'customEmojiSection',
+ text: this.$t('custom-emoji'),
+ icon: ['fas', 'asterisk'],
+ isActive: true
+ }, {
+ name: 'people',
+ ref: 'peopleSection',
+ text: this.$t('people'),
+ icon: ['far', 'laugh'],
+ isActive: false
+ }, {
+ name: 'animals_and_nature',
+ ref: 'animalsAndNatureSection',
+ text: this.$t('animals-and-nature'),
+ icon: ['fas', 'leaf'],
+ isActive: false
+ }, {
+ name: 'food_and_drink',
+ ref: 'foodAndDrinkSection',
+ text: this.$t('food-and-drink'),
+ icon: ['fas', 'utensils'],
+ isActive: false
+ }, {
+ name: 'activity',
+ ref: 'activitySection',
+ text: this.$t('activity'),
+ icon: ['fas', 'futbol'],
+ isActive: false
+ }, {
+ name: 'travel_and_places',
+ ref: 'travelAndPlacesSection',
+ text: this.$t('travel-and-places'),
+ icon: ['fas', 'city'],
+ isActive: false
+ }, {
+ name: 'objects',
+ ref: 'objectsSection',
+ text: this.$t('objects'),
+ icon: ['fas', 'poo-storm'],
+ isActive: false
+ }, {
+ name: 'symbols',
+ ref: 'symbolsSection',
+ text: this.$t('symbols'),
+ icon: ['far', 'heart'],
+ isActive: false
+ }, {
+ name: 'flags',
+ ref: 'flagsSection',
+ text: this.$t('flags'),
+ icon: ['far', 'flag'],
+ isActive: false
+ }]
+ }
+ },
+
+ created() {
+ this.customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
+ },
+
+ methods: {
+ go(ref) {
+ this.$refs.emojis.scrollTop = this.$refs[ref][0].offsetTop;
+ },
+
+ onScroll(e) {
+ const section = this.categories.forEach(x => {
+ const top = e.target.scrollTop;
+ const el = this.$refs[x.ref][0];
+ x.isActive = el.offsetTop <= top && el.offsetTop + el.offsetHeight > top;
+ });
+ },
+
+ chosen(emoji) {
+ this.$emit('chosen', emoji);
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+.prlncendiewqqkrevzeruhndoakghvtx
+ width 350px
+ background var(--face)
+
+ > header
+ display flex
+
+ > button
+ flex 1
+ padding 10px 0
+ font-size 16px
+ color var(--text)
+ transition color 0.2s ease
+
+ &:hover
+ color var(--textHighlighted)
+ transition color 0s
+
+ &.active
+ color var(--primary)
+ transition color 0s
+
+ > .emojis
+ height 300px
+ overflow-y auto
+ overflow-x hidden
+
+ > section
+ > header
+ position sticky
+ top 0
+ left 0
+ z-index 1
+ padding 8px
+ background var(--faceHeader)
+ color var(--text)
+ font-size 12px
+
+ > div
+ display grid
+ grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr
+ gap 4px
+ padding 8px
+
+ > button
+ padding 0
+ width 100%
+
+ &:before
+ content ''
+ display block
+ width 1px
+ height 0
+ padding-bottom 100%
+
+ &:hover
+ > *
+ transform scale(1.2)
+ transition transform 0s
+
+ > *
+ position absolute
+ top 0
+ left 0
+ width 100%
+ height 100%
+ font-size 28px
+ transition transform 0.2s ease
+ pointer-events none
+
+</style>
diff --git a/src/client/app/common/views/components/emoji.vue b/src/client/app/common/views/components/emoji.vue
index c57d6a944a..a8fef35b8a 100644
--- a/src/client/app/common/views/components/emoji.vue
+++ b/src/client/app/common/views/components/emoji.vue
@@ -22,7 +22,7 @@ export default Vue.extend({
},
customEmojis: {
required: false,
- default: []
+ default: () => []
}
},
diff --git a/src/client/app/desktop/views/components/emoji-picker-dialog.vue b/src/client/app/desktop/views/components/emoji-picker-dialog.vue
new file mode 100644
index 0000000000..06dbe75846
--- /dev/null
+++ b/src/client/app/desktop/views/components/emoji-picker-dialog.vue
@@ -0,0 +1,84 @@
+<template>
+<div class="gcafiosrssbtbnbzqupfmglvzgiaipyv">
+ <x-picker @chosen="chosen"/>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import contains from '../../../common/scripts/contains';
+
+export default Vue.extend({
+ components: {
+ XPicker: () => import('../../../common/views/components/emoji-picker.vue').then(m => m.default)
+ },
+
+ props: {
+ x: {
+ type: Number,
+ required: true
+ },
+ y: {
+ type: Number,
+ required: true
+ }
+ },
+
+ mounted() {
+ this.$nextTick(() => {
+ const width = this.$el.offsetWidth;
+ const height = this.$el.offsetHeight;
+
+ let x = this.x;
+ let y = this.y;
+
+ if (x + width - window.pageXOffset > window.innerWidth) {
+ x = window.innerWidth - width + window.pageXOffset;
+ }
+
+ if (y + height - window.pageYOffset > window.innerHeight) {
+ y = window.innerHeight - height + window.pageYOffset;
+ }
+
+ this.$el.style.left = x + 'px';
+ this.$el.style.top = y + 'px';
+
+ Array.from(document.querySelectorAll('body *')).forEach(el => {
+ el.addEventListener('mousedown', this.onMousedown);
+ });
+ });
+ },
+
+ methods: {
+ onMousedown(e) {
+ e.preventDefault();
+ if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close();
+ return false;
+ },
+
+ chosen(emoji) {
+ this.$emit('chosen', emoji);
+ this.close();
+ },
+
+ close() {
+ Array.from(document.querySelectorAll('body *')).forEach(el => {
+ el.removeEventListener('mousedown', this.onMousedown);
+ });
+
+ this.$emit('closed');
+ this.destroyDom();
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+.gcafiosrssbtbnbzqupfmglvzgiaipyv
+ position fixed
+ top 0
+ left 0
+ z-index 3000
+ box-shadow 0 2px 12px 0 rgba(0, 0, 0, 0.3)
+
+</style>
diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue
index 44178d9414..2bdefe94a2 100644
--- a/src/client/app/desktop/views/components/post-form.vue
+++ b/src/client/app/desktop/views/components/post-form.vue
@@ -15,11 +15,16 @@
<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a>
</div>
<input v-show="useCw" v-model="cw" :placeholder="$t('annotations')">
- <textarea :class="{ with: (files.length != 0 || poll) }"
- ref="text" v-model="text" :disabled="posting"
- @keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
- v-autocomplete="'text'"
- ></textarea>
+ <div class="textarea">
+ <textarea :class="{ with: (files.length != 0 || poll) }"
+ ref="text" v-model="text" :disabled="posting"
+ @keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
+ v-autocomplete="'text'"
+ ></textarea>
+ <button class="emoji" @click="emoji" ref="emoji">
+ <fa :icon="['far', 'laugh']"/>
+ </button>
+ </div>
<div class="files" :class="{ with: poll }" v-show="files.length != 0">
<x-draggable :list="files" :options="{ animation: 150 }">
<div v-for="file in files" :key="file.id">
@@ -377,6 +382,19 @@ export default Vue.extend({
this.visibleUsers = erase(user, this.visibleUsers);
},
+ async emoji() {
+ const Picker = await import('./emoji-picker-dialog.vue').then(m => m.default);
+ const button = this.$refs.emoji;
+ const rect = button.getBoundingClientRect();
+ const vm = this.$root.new(Picker, {
+ x: button.offsetWidth + rect.left + window.pageXOffset,
+ y: rect.top + window.pageYOffset
+ });
+ vm.$once('chosen', emoji => {
+ insertTextAtCursor(this.$refs.text, emoji);
+ });
+ },
+
post() {
this.posting = true;
@@ -469,7 +487,7 @@ export default Vue.extend({
> .content
> input
- > textarea
+ > .textarea > textarea
display block
width 100%
padding 12px
@@ -498,27 +516,48 @@ export default Vue.extend({
> input
margin-bottom 8px
- > textarea
- margin 0
- max-width 100%
- min-width 100%
- min-height 84px
+ > .textarea
+ > .emoji
+ position absolute
+ top 0
+ right 0
+ padding 10px
+ font-size 18px
+ color var(--text)
+ opacity 0.5
- &:hover
- & + *
- & + * + *
- border-color var(--primaryAlpha02)
- transition border-color .1s ease
+ &:hover
+ color var(--textHighlighted)
+ opacity 1
- &:focus
- & + *
- & + * + *
- border-color var(--primaryAlpha05)
- transition border-color 0s ease
+ &:active
+ color var(--primary)
+ opacity 1
- &.with
- border-bottom solid 1px var(--primaryAlpha01) !important
- border-radius 4px 4px 0 0
+ > textarea
+ margin 0
+ max-width 100%
+ min-width 100%
+ min-height 84px
+
+ &:hover
+ & + *
+ & + * + *
+ border-color var(--primaryAlpha02)
+ transition border-color .1s ease
+
+ &:focus
+ & + *
+ & + * + *
+ border-color var(--primaryAlpha05)
+ transition border-color 0s ease
+
+ & + .emoji
+ opacity 0.7
+
+ &.with
+ border-bottom solid 1px var(--primaryAlpha01) !important
+ border-radius 4px 4px 0 0
> .visibleUsers
margin-bottom 8px
diff --git a/src/client/theme/dark.json5 b/src/client/theme/dark.json5
index 150b6f5997..446eac557c 100644
--- a/src/client/theme/dark.json5
+++ b/src/client/theme/dark.json5
@@ -18,6 +18,7 @@
secondary: '$secondary',
bg: ':darken<8<$secondary',
text: '$text',
+ textHighlighted: ':lighten<7<$text',
scrollbarTrack: ':darken<5<$secondary',
scrollbarHandle: ':lighten<5<$secondary',
diff --git a/src/client/theme/light.json5 b/src/client/theme/light.json5
index 28b9ba7834..4a182c2428 100644
--- a/src/client/theme/light.json5
+++ b/src/client/theme/light.json5
@@ -18,6 +18,7 @@
secondary: '$secondary',
bg: ':darken<8<$secondary',
text: '$text',
+ textHighlighted: ':darken<7<$text',
scrollbarTrack: '#fff',
scrollbarHandle: '#00000033',