summaryrefslogtreecommitdiff
path: root/src/client/components
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2021-02-27 13:08:34 +0900
committersyuilo <syuilotan@yahoo.co.jp>2021-02-27 13:08:34 +0900
commitf29d417b30be5479a04a1898b883d9fa1cf11fe9 (patch)
tree64dc364352829609481c141bae38bcf4228ab5c9 /src/client/components
parentlocaleが古い場合自動更新するように (diff)
downloadsharkey-f29d417b30be5479a04a1898b883d9fa1cf11fe9.tar.gz
sharkey-f29d417b30be5479a04a1898b883d9fa1cf11fe9.tar.bz2
sharkey-f29d417b30be5479a04a1898b883d9fa1cf11fe9.zip
絵文字ピッカーを常に表示するように
Resolve #7265
Diffstat (limited to 'src/client/components')
-rw-r--r--src/client/components/emoji-picker-dialog.vue191
-rw-r--r--src/client/components/emoji-picker-window.vue197
-rw-r--r--src/client/components/emoji-picker.vue155
-rw-r--r--src/client/components/note-detailed.vue2
-rw-r--r--src/client/components/note.vue2
-rw-r--r--src/client/components/post-form.vue4
-rw-r--r--src/client/components/ui/window.vue33
7 files changed, 489 insertions, 95 deletions
diff --git a/src/client/components/emoji-picker-dialog.vue b/src/client/components/emoji-picker-dialog.vue
new file mode 100644
index 0000000000..177b5db44d
--- /dev/null
+++ b/src/client/components/emoji-picker-dialog.vue
@@ -0,0 +1,191 @@
+<template>
+<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
+ <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/>
+</MkModal>
+</template>
+
+<script lang="ts">
+import { defineComponent, markRaw } from 'vue';
+import MkModal from '@/components/ui/modal.vue';
+import MkEmojiPicker from '@/components/emoji-picker.vue';
+
+export default defineComponent({
+ components: {
+ MkModal,
+ MkEmojiPicker,
+ },
+
+ props: {
+ src: {
+ required: false
+ },
+ showPinned: {
+ required: false,
+ default: true
+ },
+ asReactionPicker: {
+ required: false
+ },
+ },
+
+ emits: ['done', 'closed'],
+
+ data() {
+ return {
+
+ };
+ },
+
+ methods: {
+ chosen(emoji: any) {
+ this.$emit('done', emoji);
+ this.$refs.modal.close();
+ },
+ }
+});
+</script>
+
+<style lang="scss" scoped>
+.omfetrab {
+ $pad: 8px;
+ --eachSize: 40px;
+
+ display: flex;
+ flex-direction: column;
+ contain: content;
+
+ &.big {
+ --eachSize: 44px;
+ }
+
+ &.w1 {
+ width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
+ }
+
+ &.w2 {
+ width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
+ }
+
+ &.w3 {
+ width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
+ }
+
+ &.h1 {
+ --height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
+ }
+
+ &.h2 {
+ --height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
+ }
+
+ &.h3 {
+ --height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
+ }
+
+ > .search {
+ width: 100%;
+ padding: 12px;
+ box-sizing: border-box;
+ font-size: 1em;
+ outline: none;
+ border: none;
+ background: transparent;
+ color: var(--fg);
+
+ &:not(.filled) {
+ order: 1;
+ z-index: 2;
+ box-shadow: 0px -1px 0 0px var(--divider);
+ }
+ }
+
+ > .emojis {
+ height: var(--height);
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ scrollbar-width: none;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ > .index {
+ min-height: var(--height);
+ position: relative;
+ border-bottom: solid 1px var(--divider);
+
+ > .arrow {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ padding: 16px 0;
+ text-align: center;
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ }
+
+ section {
+ > header {
+ position: sticky;
+ top: 0;
+ left: 0;
+ z-index: 1;
+ padding: 8px;
+ font-size: 12px;
+ }
+
+ > div {
+ padding: $pad;
+
+ > button {
+ position: relative;
+ padding: 0;
+ width: var(--eachSize);
+ height: var(--eachSize);
+ border-radius: 4px;
+
+ &:focus {
+ outline: solid 2px var(--focus);
+ z-index: 1;
+ }
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.05);
+ }
+
+ &:active {
+ background: var(--accent);
+ box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
+ }
+
+ > * {
+ font-size: 24px;
+ height: 1.25em;
+ vertical-align: -.25em;
+ pointer-events: none;
+ }
+ }
+ }
+
+ &.result {
+ border-bottom: solid 1px var(--divider);
+
+ &:empty {
+ display: none;
+ }
+ }
+
+ &.unicode {
+ min-height: 384px;
+ }
+
+ &.custom {
+ min-height: 64px;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/client/components/emoji-picker-window.vue b/src/client/components/emoji-picker-window.vue
new file mode 100644
index 0000000000..fb6a5a9072
--- /dev/null
+++ b/src/client/components/emoji-picker-window.vue
@@ -0,0 +1,197 @@
+<template>
+<MkWindow ref="window"
+ :initial-width="null"
+ :initial-height="null"
+ :can-resize="false"
+ :mini="true"
+ :front="true"
+ @closed="$emit('closed')"
+>
+ <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/>
+</MkWindow>
+</template>
+
+<script lang="ts">
+import { defineComponent, markRaw } from 'vue';
+import MkWindow from '@/components/ui/window.vue';
+import MkEmojiPicker from '@/components/emoji-picker.vue';
+
+export default defineComponent({
+ components: {
+ MkWindow,
+ MkEmojiPicker,
+ },
+
+ props: {
+ src: {
+ required: false
+ },
+ showPinned: {
+ required: false,
+ default: true
+ },
+ asReactionPicker: {
+ required: false
+ },
+ },
+
+ emits: ['chosen', 'closed'],
+
+ data() {
+ return {
+
+ };
+ },
+
+ methods: {
+ chosen(emoji: any) {
+ this.$emit('chosen', emoji);
+ },
+ }
+});
+</script>
+
+<style lang="scss" scoped>
+.omfetrab {
+ $pad: 8px;
+ --eachSize: 40px;
+
+ display: flex;
+ flex-direction: column;
+ contain: content;
+
+ &.big {
+ --eachSize: 44px;
+ }
+
+ &.w1 {
+ width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
+ }
+
+ &.w2 {
+ width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
+ }
+
+ &.w3 {
+ width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
+ }
+
+ &.h1 {
+ --height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
+ }
+
+ &.h2 {
+ --height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
+ }
+
+ &.h3 {
+ --height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
+ }
+
+ > .search {
+ width: 100%;
+ padding: 12px;
+ box-sizing: border-box;
+ font-size: 1em;
+ outline: none;
+ border: none;
+ background: transparent;
+ color: var(--fg);
+
+ &:not(.filled) {
+ order: 1;
+ z-index: 2;
+ box-shadow: 0px -1px 0 0px var(--divider);
+ }
+ }
+
+ > .emojis {
+ height: var(--height);
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ scrollbar-width: none;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ > .index {
+ min-height: var(--height);
+ position: relative;
+ border-bottom: solid 1px var(--divider);
+
+ > .arrow {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ padding: 16px 0;
+ text-align: center;
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ }
+
+ section {
+ > header {
+ position: sticky;
+ top: 0;
+ left: 0;
+ z-index: 1;
+ padding: 8px;
+ font-size: 12px;
+ }
+
+ > div {
+ padding: $pad;
+
+ > button {
+ position: relative;
+ padding: 0;
+ width: var(--eachSize);
+ height: var(--eachSize);
+ border-radius: 4px;
+
+ &:focus {
+ outline: solid 2px var(--focus);
+ z-index: 1;
+ }
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.05);
+ }
+
+ &:active {
+ background: var(--accent);
+ box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
+ }
+
+ > * {
+ font-size: 24px;
+ height: 1.25em;
+ vertical-align: -.25em;
+ pointer-events: none;
+ }
+ }
+ }
+
+ &.result {
+ border-bottom: solid 1px var(--divider);
+
+ &:empty {
+ display: none;
+ }
+ }
+
+ &.unicode {
+ min-height: 384px;
+ }
+
+ &.custom {
+ min-height: 64px;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue
index 9a261ef83f..93530e16c8 100644
--- a/src/client/components/emoji-picker.vue
+++ b/src/client/components/emoji-picker.vue
@@ -1,93 +1,91 @@
<template>
-<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
- <div class="omfetrab _popup" :class="['w' + width, 'h' + height, { big }]">
- <input ref="search" class="search" :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()">
- <div class="emojis" ref="emojis">
- <section class="result">
- <div v-if="searchResultCustom.length > 0">
- <button v-for="emoji in searchResultCustom"
- class="_button"
- :title="emoji.name"
- @click="chosen(emoji, $event)"
- :key="emoji"
- tabindex="0"
- >
- <MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>
- <img v-else :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
- </button>
- </div>
- <div v-if="searchResultUnicode.length > 0">
- <button v-for="emoji in searchResultUnicode"
- class="_button"
- :title="emoji.name"
- @click="chosen(emoji, $event)"
- :key="emoji.name"
- tabindex="0"
- >
- <MkEmoji :emoji="emoji.char"/>
- </button>
- </div>
- </section>
-
- <div class="index">
- <section v-if="showPinned">
- <div>
- <button v-for="emoji in pinned"
- class="_button"
- @click="chosen(emoji, $event)"
- tabindex="0"
- >
- <MkEmoji :emoji="emoji" :normal="true"/>
- </button>
- </div>
- </section>
-
- <section>
- <header class="_acrylic"><Fa :icon="faClock" fixed-width/> {{ $ts.recentUsed }}</header>
- <div>
- <button v-for="emoji in $store.state.recentlyUsedEmojis"
- class="_button"
- @click="chosen(emoji, $event)"
- :key="emoji"
- >
- <MkEmoji :emoji="emoji" :normal="true"/>
- </button>
- </div>
- </section>
-
- <div class="arrow"><Fa :icon="faChevronDown"/></div>
+<div class="omfetrab _popup" :class="['w' + width, 'h' + height, { big }]">
+ <input ref="search" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()">
+ <div class="emojis" ref="emojis">
+ <section class="result">
+ <div v-if="searchResultCustom.length > 0">
+ <button v-for="emoji in searchResultCustom"
+ class="_button"
+ :title="emoji.name"
+ @click="chosen(emoji, $event)"
+ :key="emoji"
+ tabindex="0"
+ >
+ <MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>
+ <img v-else :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
+ </button>
</div>
+ <div v-if="searchResultUnicode.length > 0">
+ <button v-for="emoji in searchResultUnicode"
+ class="_button"
+ :title="emoji.name"
+ @click="chosen(emoji, $event)"
+ :key="emoji.name"
+ tabindex="0"
+ >
+ <MkEmoji :emoji="emoji.char"/>
+ </button>
+ </div>
+ </section>
- <section v-for="category in customEmojiCategories" :key="'custom:' + category" class="custom">
- <header class="_acrylic" v-appear="() => visibleCategories[category] = true">{{ category || $ts.other }}</header>
- <div v-if="visibleCategories[category]">
- <button v-for="emoji in customEmojis.filter(e => e.category === category)"
+ <div class="index">
+ <section v-if="showPinned">
+ <div>
+ <button v-for="emoji in pinned"
class="_button"
- :title="emoji.name"
@click="chosen(emoji, $event)"
- :key="emoji.name"
+ tabindex="0"
>
- <img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
+ <MkEmoji :emoji="emoji" :normal="true"/>
</button>
</div>
</section>
- <section v-for="category in categories" :key="category.name" class="unicode">
- <header class="_acrylic" v-appear="() => category.isActive = true"><Fa :icon="category.icon" fixed-width/> {{ category.name }}</header>
- <div v-if="category.isActive">
- <button v-for="emoji in emojilist.filter(e => e.category === category.name)"
+ <section>
+ <header class="_acrylic"><Fa :icon="faClock" fixed-width/> {{ $ts.recentUsed }}</header>
+ <div>
+ <button v-for="emoji in $store.state.recentlyUsedEmojis"
class="_button"
- :title="emoji.name"
@click="chosen(emoji, $event)"
- :key="emoji.name"
+ :key="emoji"
>
- <MkEmoji :emoji="emoji.char"/>
+ <MkEmoji :emoji="emoji" :normal="true"/>
</button>
</div>
</section>
+
+ <div class="arrow"><Fa :icon="faChevronDown"/></div>
</div>
+
+ <section v-for="category in customEmojiCategories" :key="'custom:' + category" class="custom">
+ <header class="_acrylic" v-appear="() => visibleCategories[category] = true">{{ category || $ts.other }}</header>
+ <div v-if="visibleCategories[category]">
+ <button v-for="emoji in customEmojis.filter(e => e.category === category)"
+ class="_button"
+ :title="emoji.name"
+ @click="chosen(emoji, $event)"
+ :key="emoji.name"
+ >
+ <img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
+ </button>
+ </div>
+ </section>
+
+ <section v-for="category in categories" :key="category.name" class="unicode">
+ <header class="_acrylic" v-appear="() => category.isActive = true"><Fa :icon="category.icon" fixed-width/> {{ category.name }}</header>
+ <div v-if="category.isActive">
+ <button v-for="emoji in emojilist.filter(e => e.category === category.name)"
+ class="_button"
+ :title="emoji.name"
+ @click="chosen(emoji, $event)"
+ :key="emoji.name"
+ >
+ <MkEmoji :emoji="emoji.char"/>
+ </button>
+ </div>
+ </section>
</div>
-</MkModal>
+</div>
</template>
<script lang="ts">
@@ -96,7 +94,6 @@ import { emojilist } from '../../misc/emojilist';
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faClock, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons';
import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons';
-import MkModal from '@/components/ui/modal.vue';
import Particle from '@/components/particle.vue';
import * as os from '@/os';
import { isDeviceTouch } from '@/scripts/is-device-touch';
@@ -104,14 +101,7 @@ import { isMobile } from '@/scripts/is-mobile';
import { emojiCategories } from '@/instance';
export default defineComponent({
- components: {
- MkModal,
- },
-
props: {
- src: {
- required: false
- },
showPinned: {
required: false,
default: true
@@ -121,7 +111,7 @@ export default defineComponent({
},
},
- emits: ['done', 'closed'],
+ emits: ['chosen'],
data() {
return {
@@ -345,8 +335,7 @@ export default defineComponent({
}
const key = this.getKey(emoji);
- this.$emit('done', key);
- this.$refs.modal.close();
+ this.$emit('chosen', key);
// 最近使った絵文字更新
if (!this.pinned.includes(key)) {
diff --git a/src/client/components/note-detailed.vue b/src/client/components/note-detailed.vue
index 83157f7d59..e1927133ae 100644
--- a/src/client/components/note-detailed.vue
+++ b/src/client/components/note-detailed.vue
@@ -523,7 +523,7 @@ export default defineComponent({
react(viaKeyboard = false) {
pleaseLogin();
this.blur();
- os.popup(import('@/components/emoji-picker.vue'), {
+ os.popup(import('@/components/emoji-picker-dialog.vue'), {
src: this.$refs.reactButton,
asReactionPicker: true
}, {
diff --git a/src/client/components/note.vue b/src/client/components/note.vue
index 377e5991b2..6af0668e2e 100644
--- a/src/client/components/note.vue
+++ b/src/client/components/note.vue
@@ -498,7 +498,7 @@ export default defineComponent({
react(viaKeyboard = false) {
pleaseLogin();
this.blur();
- os.popup(import('@/components/emoji-picker.vue'), {
+ os.popup(import('@/components/emoji-picker-dialog.vue'), {
src: this.$refs.reactButton,
asReactionPicker: true
}, {
diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue
index 7849095ba8..9b5adf9cc1 100644
--- a/src/client/components/post-form.vue
+++ b/src/client/components/post-form.vue
@@ -606,9 +606,7 @@ export default defineComponent({
},
async insertEmoji(ev) {
- os.pickEmoji(ev.currentTarget || ev.target).then(emoji => {
- insertTextAtCursor(this.$refs.text, emoji);
- });
+ os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text);
},
showActions(ev) {
diff --git a/src/client/components/ui/window.vue b/src/client/components/ui/window.vue
index d8852b9ffd..4d3b048c11 100644
--- a/src/client/components/ui/window.vue
+++ b/src/client/components/ui/window.vue
@@ -1,8 +1,8 @@
<template>
<transition :name="$store.state.animation ? 'window' : ''" appear @after-leave="$emit('closed')">
- <div class="ebkgocck" v-if="showing">
+ <div class="ebkgocck" :class="{ front }" v-if="showing">
<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown">
- <div class="header" @contextmenu.prevent.stop="onContextmenu">
+ <div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu">
<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot>
<button v-else class="_button" @click="close()"><Fa :icon="faTimes"/></button>
@@ -92,6 +92,16 @@ export default defineComponent({
required: false,
default: false,
},
+ mini: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ front: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
contextmenu: {
type: Array,
required: false,
@@ -387,6 +397,10 @@ export default defineComponent({
left: 0;
z-index: 5000;
+ &.front {
+ z-index: 11000; // front指定の時は、mk-modalのよりも大きくなければならない
+ }
+
> .body {
overflow: hidden; // overflow: clip; をSafariが対応したら消す
overflow: clip;
@@ -397,17 +411,22 @@ export default defineComponent({
height: 100%;
> .header {
- $height: 50px;
+ --height: 50px;
+
+ &.mini {
+ --height: 38px;
+ }
+
display: flex;
position: relative;
z-index: 1;
flex-shrink: 0;
user-select: none;
- height: $height;
+ height: var(--height);
> ::v-deep(button) {
- height: $height;
- width: $height;
+ height: var(--height);
+ width: var(--height);
&:hover {
color: var(--fgHighlighted);
@@ -417,7 +436,7 @@ export default defineComponent({
> .title {
flex: 1;
position: relative;
- line-height: $height;
+ line-height: var(--height);
white-space: nowrap;
overflow: hidden; // overflow: clip; をSafariが対応したら消す
overflow: clip;