diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-03-16 10:58:06 +0900 |
|---|---|---|
| committer | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-03-16 10:58:06 +0900 |
| commit | c2940fd77cba7b0cf912db9fda92e66dbce9110f (patch) | |
| tree | 1ac1df00c5ea467af720e2c96bf3517d4d1d59b3 | |
| parent | refactor (diff) | |
| download | sharkey-c2940fd77cba7b0cf912db9fda92e66dbce9110f.tar.gz sharkey-c2940fd77cba7b0cf912db9fda92e66dbce9110f.tar.bz2 sharkey-c2940fd77cba7b0cf912db9fda92e66dbce9110f.zip | |
enhance(frontend): improve usability on touch device
| -rw-r--r-- | locales/index.d.ts | 8 | ||||
| -rw-r--r-- | locales/ja-JP.yml | 2 | ||||
| -rw-r--r-- | packages/frontend/src/boot/common.ts | 4 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkDialog.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkInfo.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkInput.vue | 8 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkInviteCode.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkKeyValue.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNote.vue | 3 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNoteDetailed.vue | 3 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkTextarea.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/accessibility.vue | 11 | ||||
| -rw-r--r-- | packages/frontend/src/preferences/def.ts | 3 | ||||
| -rw-r--r-- | packages/frontend/src/style.scss | 17 | ||||
| -rw-r--r-- | packages/frontend/src/utility/autogen/settings-search-index.ts | 13 |
15 files changed, 69 insertions, 17 deletions
diff --git a/locales/index.d.ts b/locales/index.d.ts index 00d275ee43..7e4892eb39 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5433,6 +5433,14 @@ export interface Locale extends ILocale { * タイムラインとノート */ "timelineAndNote": string; + /** + * 全てのテキスト要素を選択可能にする + */ + "makeEveryTextElementsSelectable": string; + /** + * 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。 + */ + "makeEveryTextElementsSelectable_description": string; }; "_preferencesProfile": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8e787fa02f..f17c5a1278 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1357,6 +1357,8 @@ _settings: appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。" soundsBanner: "クライアントで再生するサウンドの設定が行えます。" timelineAndNote: "タイムラインとノート" + makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする" + makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。" _preferencesProfile: profileName: "プロファイル名" diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 73c4256c4b..10bcddbde7 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -234,6 +234,10 @@ export async function common(createVue: () => App<Element>) { }); } + if (prefer.s.makeEveryTextElementsSelectable) { + document.documentElement.classList.add('forceSelectableAll'); + } + //#region Fetch user if ($i && $i.token) { if (_DEV_) { diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 6c9fa3167a..312c7ab58f 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i> <MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/> </div> - <header v-if="title" :class="$style.title"><Mfm :text="title"/></header> - <div v-if="text" :class="$style.text"><Mfm :text="text"/></div> + <header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header> + <div v-if="text" :class="$style.text" class="_selectable"><Mfm :text="text"/></div> <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown"> <template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> <template #caption> diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue index 90ca1b5a9d..f114ec8a71 100644 --- a/packages/frontend/src/components/MkInfo.vue +++ b/packages/frontend/src/components/MkInfo.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="[$style.root, { [$style.warn]: warn }]"> +<div :class="[$style.root, { [$style.warn]: warn }]" class="_selectable"> <i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i> <i v-else class="ti ti-info-circle" :class="$style.i"></i> <div><slot></slot></div> diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index aff9a67020..a66e64311a 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div> +<div class="_selectable"> <div :class="$style.label" @click="focus"><slot name="label"></slot></div> <div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]"> <div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div> @@ -45,13 +45,13 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; -import type { InputHTMLAttributes } from 'vue'; import { debounce } from 'throttle-debounce'; -import MkButton from '@/components/MkButton.vue'; import { useInterval } from '@@/js/use-interval.js'; +import type { InputHTMLAttributes } from 'vue'; +import type { SuggestionType } from '@/utility/autocomplete.js'; +import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; import { Autocomplete } from '@/utility/autocomplete.js'; -import type { SuggestionType } from '@/utility/autocomplete.js'; const props = defineProps<{ modelValue: string | number | null; diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue index 08eddfc03c..0f53d1573d 100644 --- a/packages/frontend/src/components/MkInviteCode.vue +++ b/packages/frontend/src/components/MkInviteCode.vue @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.items"> <div> <div :class="$style.label">{{ i18n.ts.invitationCode }}</div> - <div>{{ invite.code }}</div> + <div class="_selectableAtomic">{{ invite.code }}</div> </div> <div v-if="moderator"> <div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div> diff --git a/packages/frontend/src/components/MkKeyValue.vue b/packages/frontend/src/components/MkKeyValue.vue index 6a4b298523..275b153e4a 100644 --- a/packages/frontend/src/components/MkKeyValue.vue +++ b/packages/frontend/src/components/MkKeyValue.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.key"> <slot name="key"></slot> </div> - <div :class="$style.value"> + <div :class="$style.value" class="_selectable"> <slot name="value"></slot> <button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button> </div> diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index e08f5e6163..1f18b173a0 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -76,12 +76,13 @@ SPDX-License-Identifier: AGPL-3.0-only :emojiUrls="appearNote.emojis" :enableEmojiMenu="true" :enableEmojiMenuReaction="true" + class="_selectable" /> <div v-if="translating || translation" :class="$style.translation"> <MkLoading v-if="translating" mini/> <div v-else-if="translation"> <b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b> - <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> + <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/> </div> </div> </div> diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index dd8d3567b2..6af8acb2df 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -97,13 +97,14 @@ SPDX-License-Identifier: AGPL-3.0-only :emojiUrls="appearNote.emojis" :enableEmojiMenu="true" :enableEmojiMenuReaction="true" + class="_selectable" /> <a v-if="appearNote.renote != null" :class="$style.rn">RN:</a> <div v-if="translating || translation" :class="$style.translation"> <MkLoading v-if="translating" mini/> <div v-else-if="translation"> <b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b> - <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> + <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/> </div> </div> <div v-if="appearNote.files && appearNote.files.length > 0"> diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue index eb47a8b858..c9da226734 100644 --- a/packages/frontend/src/components/MkTextarea.vue +++ b/packages/frontend/src/components/MkTextarea.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div> +<div class="_selectable"> <div :class="$style.label" @click="focus"><slot name="label"></slot></div> <div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;"> <textarea @@ -38,10 +38,10 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue'; import { debounce } from 'throttle-debounce'; +import type { SuggestionType } from '@/utility/autocomplete.js'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; import { Autocomplete } from '@/utility/autocomplete.js'; -import type { SuggestionType } from '@/utility/autocomplete.js'; const props = defineProps<{ modelValue: string | null; diff --git a/packages/frontend/src/pages/settings/accessibility.vue b/packages/frontend/src/pages/settings/accessibility.vue index f7b1e7d2a0..e8268719f5 100644 --- a/packages/frontend/src/pages/settings/accessibility.vue +++ b/packages/frontend/src/pages/settings/accessibility.vue @@ -58,6 +58,15 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </MkPreferenceContainer> </SearchMarker> + + <SearchMarker :keywords="['text', 'selectable']"> + <MkPreferenceContainer k="makeEveryTextElementsSelectable"> + <MkSwitch v-model="makeEveryTextElementsSelectable"> + <template #label><SearchLabel>{{ i18n.ts._settings.makeEveryTextElementsSelectable }}</SearchLabel></template> + <template #caption>{{ i18n.ts._settings.makeEveryTextElementsSelectable_description }}</template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> </div> <SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']"> @@ -122,6 +131,7 @@ const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe'); const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer'); const contextMenu = prefer.model('contextMenu'); const menuStyle = prefer.model('menuStyle'); +const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable'); const fontSize = ref(miLocalStorage.getItem('fontSize')); const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); @@ -147,6 +157,7 @@ watch([ contextMenu, fontSize, useSystemFont, + makeEveryTextElementsSelectable, ], async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index e460359acd..048392ef49 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -313,6 +313,9 @@ export const PREF_DEF = { defaultFollowWithReplies: { default: false, }, + makeEveryTextElementsSelectable: { + default: DEFAULT_DEVICE_KIND === 'desktop', + }, plugins: { default: [] as Plugin[], }, diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index 5308c312bb..43aff3012e 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -81,6 +81,11 @@ html { &.useSystemFont { font-family: system-ui; } + + &:not(.forceSelectableAll) { + user-select: none; + -webkit-user-select: none; + } } html._themeChanging_ { @@ -120,6 +125,8 @@ a { textarea, input { tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent; + user-select: text; + -webkit-user-select: text; } optgroup, option { @@ -184,6 +191,16 @@ rt { padding: 0.3em 0.5em; } +._selectable { + user-select: text; + -webkit-user-select: text; +} + +._selectableAtomic { + user-select: all; + -webkit-user-select: all; +} + ._noSelect { user-select: none; -webkit-user-select: none; diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts index ab26d671a2..2a0a4c1857 100644 --- a/packages/frontend/src/utility/autogen/settings-search-index.ts +++ b/packages/frontend/src/utility/autogen/settings-search-index.ts @@ -897,22 +897,27 @@ export const searchIndexes: SearchIndexItem[] = [ keywords: ['native', 'system', 'video', 'audio', 'player', 'media'], }, { - id: '1fV9WINCQ', + id: 'b1GYEEJeh', + label: i18n.ts._settings.makeEveryTextElementsSelectable, + keywords: ['text', 'selectable'], + }, + { + id: 'vVLxwINTJ', label: i18n.ts.menuStyle, keywords: ['menu', 'style', 'popup', 'drawer'], }, { - id: 'mLQzlKUNu', + id: '14cMhMLHL', label: i18n.ts._contextMenu.title, keywords: ['contextmenu', 'system', 'native'], }, { - id: 'yP96aA3j9', + id: 'oSo4LXMX9', label: i18n.ts.fontSize, keywords: ['font', 'size'], }, { - id: 'jQeiMopFE', + id: '7LQSAThST', label: i18n.ts.useSystemFont, keywords: ['font', 'system', 'native'], }, |