improve(friendly): Improved design of note post form in mobile environment

This commit is contained in:
NoriDev 2022-09-12 01:32:31 +09:00
parent 8bc7fae241
commit 3dab9d32df
3 changed files with 59 additions and 5 deletions

View file

@ -15,6 +15,7 @@
- 클라이언트: (friendly) 모바일 환경에서 스크롤을 내리면 플로팅 버튼이 감춰지도록 변경 - 클라이언트: (friendly) 모바일 환경에서 스크롤을 내리면 플로팅 버튼이 감춰지도록 변경
- 클라이언트: (friendly) 서버와 연결이 끊어졌을 때 10초간 경고를 표시한 후 자동으로 숨기기 - 클라이언트: (friendly) 서버와 연결이 끊어졌을 때 10초간 경고를 표시한 후 자동으로 숨기기
- 클라이언트: (friendly) 서버와 연결이 끊어졌을 때 헤더에 연결 끊김 표시 - 클라이언트: (friendly) 서버와 연결이 끊어졌을 때 헤더에 연결 끊김 표시
- 클라이언트: (friendly) 모바일 환경에서 노트 작성 폼의 디자인 개선
- 클라이언트: Google Translate 서비스 추가 (thanks to @ltlapy) - 클라이언트: Google Translate 서비스 추가 (thanks to @ltlapy)
- 클라이언트: DeepL과 Google Translate를 선택할 수 있는 옵션 추가 - 클라이언트: DeepL과 Google Translate를 선택할 수 있는 옵션 추가
- 클라이언트: Enter 키를 눌러 보내기 옵션 추가 - 클라이언트: Enter 키를 눌러 보내기 옵션 추가

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
v-size="{ max: [310, 500] }" class="gafaadew" v-size="{ max: [310, 500] }" class="gafaadew"
:class="{ modal, _popup: modal, friendly: isFriendly }" :class="{ modal, _popup: modal, friendly: isFriendly, 'friendly-not-desktop': isFriendly && !isDesktop }"
@dragover.stop="onDragover" @dragover.stop="onDragover"
@dragenter="onDragenter" @dragenter="onDragenter"
@dragleave="onDragleave" @dragleave="onDragleave"
@ -41,7 +41,7 @@
</div> </div>
<MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo> <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
<input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown"> <input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
<textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> <textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw, 'friendly-not-desktop': isFriendly && !isDesktop }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.ts.hashtags" list="hashtags"> <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
<XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> <XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
@ -64,7 +64,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent } from 'vue'; import { inject, watch, nextTick, onMounted, defineAsyncComponent, ref } from 'vue';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
@ -90,8 +90,18 @@ import { i18n } from '@/i18n';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
import { uploadFile } from '@/scripts/upload'; import { uploadFile } from '@/scripts/upload';
import { deviceKind } from '@/scripts/device-kind';
const isFriendly = $ref(localStorage.getItem('ui') === 'friendly'); const isFriendly = $ref(localStorage.getItem('ui') === 'friendly');
const DESKTOP_THRESHOLD = 1100;
const MOBILE_THRESHOLD = 500;
// UI deviceKind === 'desktop'
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
window.addEventListener('resize', () => {
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
});
const modal = inject('modal'); const modal = inject('modal');
@ -720,6 +730,12 @@ onMounted(() => {
nextTick(() => watchForDraft()); nextTick(() => watchForDraft());
}); });
if (!isDesktop.value) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
}, { passive: true });
}
}); });
</script> </script>
@ -736,6 +752,13 @@ onMounted(() => {
max-width: 800px; max-width: 800px;
} }
&.friendly-not-desktop {
margin: initial !important;
padding: initial !important;
border-radius: initial !important;
max-width: 100% !important;
}
> header { > header {
z-index: 1000; z-index: 1000;
height: 66px; height: 66px;
@ -924,6 +947,10 @@ onMounted(() => {
&.withCw { &.withCw {
padding-top: 8px; padding-top: 8px;
} }
&.friendly-not-desktop {
min-height: 200px !important;
}
} }
> footer { > footer {
@ -984,6 +1011,10 @@ onMounted(() => {
min-height: 80px; min-height: 80px;
} }
&.friendly-not-desktop {
min-height: 200px !important;
}
> footer { > footer {
padding: 0 8px 8px 8px; padding: 0 8px 8px 8px;
} }

View file

@ -2,7 +2,7 @@
<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened"> <transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> <div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div> <div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick"> <div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top', friendly: isFriendly && !isDesktop }" :style="{ zIndex }" @click.self="onBgClick">
<slot :max-height="maxHeight" :type="type"></slot> <slot :max-height="maxHeight" :type="type"></slot>
</div> </div>
</div> </div>
@ -10,12 +10,23 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, watch, provide } from 'vue'; import { nextTick, onMounted, watch, provide, ref } from 'vue';
import * as os from '@/os'; import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch'; import { isTouchUsing } from '@/scripts/touch';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { deviceKind } from '@/scripts/device-kind'; import { deviceKind } from '@/scripts/device-kind';
const isFriendly = $ref(localStorage.getItem('ui') === 'friendly');
const DESKTOP_THRESHOLD = 1100;
const MOBILE_THRESHOLD = 500;
// UI deviceKind === 'desktop'
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
window.addEventListener('resize', () => {
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
});
function getFixedContainer(el: Element | null): Element | null { function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === 'BODY') return null; if (el == null || el.tagName === 'BODY') return null;
const position = window.getComputedStyle(el).getPropertyValue('position'); const position = window.getComputedStyle(el).getPropertyValue('position');
@ -246,6 +257,12 @@ onMounted(() => {
align(); align();
}).observe(content!); }).observe(content!);
}); });
if (!isDesktop.value) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
}, { passive: true });
}
}); });
defineExpose({ defineExpose({
@ -368,6 +385,11 @@ defineExpose({
margin-top: 0; margin-top: 0;
} }
} }
&.friendly {
padding: initial;
mask-image: initial;
}
} }
} }