diff options
Diffstat (limited to 'packages/frontend/src/components')
| -rw-r--r-- | packages/frontend/src/components/MkMenu.vue | 3 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkPostForm.TextCounter.vue | 95 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkPostForm.vue | 54 |
3 files changed, 145 insertions, 7 deletions
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index aa53c19c33..e3c27c5f6e 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -35,6 +35,9 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]"> <span><MkEllipsis/></span> </span> + <div v-else-if="item.type === 'component'" role="menuitem" tabindex="-1" :class="[$style.componentItem]"> + <component :is="item.component" v-bind="item.props"/> + </div> <MkA v-else-if="item.type === 'link'" role="menuitem" diff --git a/packages/frontend/src/components/MkPostForm.TextCounter.vue b/packages/frontend/src/components/MkPostForm.TextCounter.vue new file mode 100644 index 0000000000..b1d39df5d3 --- /dev/null +++ b/packages/frontend/src/components/MkPostForm.TextCounter.vue @@ -0,0 +1,95 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[$style.textCountRoot]"> + <div :class="$style.textCountLabel">{{ i18n.ts.textCount }}</div> + <div + :class="[$style.textCount, + { [$style.danger]: textCountPercentage > 100 }, + { [$style.warning]: textCountPercentage > 90 && textCountPercentage <= 100 }, + ]" + > + <div :class="$style.textCountGraph"></div> + <div><span :class="$style.textCountCurrent">{{ number(textLength) }}</span> / {{ number(maxTextLength) }}</div> + </div> +</div> +</template> + +<script lang="ts" setup> +import { computed, useTemplateRef } from 'vue'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; +import number from '@/filters/number.js'; + +const props = defineProps<{ + textLength: number; +}>(); + +const maxTextLength = computed(() => { + return instance ? instance.maxNoteTextLength : 1000; +}); + +const textCountPercentage = computed(() => { + return props.textLength / maxTextLength.value * 100; +}); +</script> + +<style lang="scss" module> +.textCountRoot { + padding: 4px 14px; +} + +.textCountLabel { + font-size: 11px; + opacity: 0.8; + margin-bottom: 4px; +} + +.textCount { + display: flex; + gap: var(--MI-marginHalf); + align-items: center; + font-size: 12px; + --countColor: var(--MI_THEME-accent); + + &.danger { + --countColor: var(--MI_THEME-error); + } + + &.warning { + --countColor: var(--MI_THEME-warn); + } + + .textCountGraph { + position: relative; + width: 24px; + height: 24px; + border-radius: 50%; + background-image: conic-gradient( + var(--countColor) 0% v-bind("Math.min(100, textCountPercentage) + '%'"), + rgba(0, 0, 0, .2) v-bind("Math.min(100, textCountPercentage) + '%'") 100% + ); + + &::after { + content: ''; + position: absolute; + width: 16px; + height: 16px; + border-radius: 50%; + background-color: var(--MI_THEME-popup); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + } + + .textCountCurrent { + color: var(--countColor); + font-weight: 700; + font-size: 18px; + } +} +</style> diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index d57300f647..eb278a0d7e 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div :class="$style.headerRight"> <template v-if="!(channel != null && fixed)"> - <button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility"> + <button v-if="channel == null" ref="visibilityButton" v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility"> <span v-if="visibility === 'public'"><i class="ti ti-world"></i></span> <span v-if="visibility === 'home'"><i class="ti ti-home"></i></span> <span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span> @@ -32,15 +32,11 @@ SPDX-License-Identifier: AGPL-3.0-only <span :class="$style.headerRightButtonText">{{ channel.name }}</span> </button> </template> - <button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly"> + <button v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly"> <span v-if="!localOnly"><i class="ti ti-rocket"></i></span> <span v-else><i class="ti ti-rocket-off"></i></span> </button> - <button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance"> - <span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span> - <span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span> - <span v-else><i class="ti ti-icons"></i></span> - </button> + <button ref="otherSettingsButton" v-tooltip="i18n.ts.other" class="_button" :class="$style.headerRightItem" @click="showOtherSettings"><i class="ti ti-dots"></i></button> <button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post"> <div :class="$style.submitInner"> <template v-if="posted"></template> @@ -111,9 +107,11 @@ import { toASCII } from 'punycode.js'; import { host, url } from '@@/js/config.js'; import type { ShallowRef } from 'vue'; import type { PostFormProps } from '@/types/post-form.js'; +import type { MenuItem } from '@/types/menu.js'; import type { PollEditorModelValue } from '@/components/MkPollEditor.vue'; import MkNotePreview from '@/components/MkNotePreview.vue'; import XPostFormAttaches from '@/components/MkPostFormAttaches.vue'; +import XTextCounter from '@/components/MkPostForm.TextCounter.vue'; import MkPollEditor from '@/components/MkPollEditor.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import { erase, unique } from '@/utility/array.js'; @@ -171,6 +169,7 @@ const textareaEl = shallowRef<HTMLTextAreaElement | null>(null); const cwInputEl = shallowRef<HTMLInputElement | null>(null); const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null); const visibilityButton = shallowRef<HTMLElement>(); +const otherSettingsButton = shallowRef<HTMLElement>(); const posting = ref(false); const posted = ref(false); @@ -556,6 +555,47 @@ async function toggleReactionAcceptance() { reactionAcceptance.value = select.result; } +//#region その他の設定メニューpopup +function showOtherSettings() { + let reactionAcceptanceIcon = 'ti ti-icons'; + + if (reactionAcceptance.value === 'likeOnly') { + reactionAcceptanceIcon = 'ti ti-heart _love'; + } else if (reactionAcceptance.value === 'likeOnlyForRemote') { + reactionAcceptanceIcon = 'ti ti-heart-plus'; + } + + const menuItems = [{ + type: 'component', + component: XTextCounter, + props: { + textLength: textLength, + }, + }, { type: 'divider' }, { + icon: reactionAcceptanceIcon, + text: i18n.ts.reactionAcceptance, + action: () => { + toggleReactionAcceptance(); + }, + }, { type: 'divider' }, { + icon: 'ti ti-trash', + text: i18n.ts.reset, + danger: true, + action: async () => { + if (props.mock) return; + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.ts.resetAreYouSure, + }); + if (canceled) return; + clear(); + }, + }] satisfies MenuItem[]; + + os.popupMenu(menuItems, otherSettingsButton.value); +} +//#endregion + function pushVisibleUser(user: Misskey.entities.UserDetailed) { if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) { visibleUsers.value.push(user); |