Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance(frontend): チャンネルへのノート投稿導線を改善 #13028

Open
wants to merge 51 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
20e4c6b
投稿フォームでどこからでもチャンネルに投げられるようにしたい
fruitriin Dec 16, 2023
704ebcd
開いてるページで投稿先がチャンネルになってほしい
fruitriin Dec 16, 2023
88f0714
fix lint
fruitriin Dec 16, 2023
86ac1f6
fix lint
fruitriin Dec 16, 2023
1f9c2ee
:art:
fruitriin Dec 16, 2023
d5fc58e
fix bug
fruitriin Dec 16, 2023
2219115
fix lint
fruitriin Dec 16, 2023
63e867c
fix lint
fruitriin Dec 16, 2023
6425c43
Merge branch 'develop' into feature/post-channel-everywhere
fruitriin Dec 17, 2023
f680469
Merge branch 'develop' into feature/default-post-target-detect-from-path
fruitriin Dec 17, 2023
974acf4
fix MkVisibilityPicker.vue
samunohito Dec 18, 2023
fb3288d
fix MkPostForm.vue
samunohito Dec 18, 2023
785c262
fix api
samunohito Dec 18, 2023
5faa410
Merge pull request #11 from samunohito/feature/post-channel-everywher…
fruitriin Dec 18, 2023
ad0a5d8
Merge branch 'develop' of github.com:misskey-dev/misskey into feature…
mesichicken Jan 18, 2024
125acea
Merge branch 'develop' of github.com:misskey-dev/misskey into feature…
mesichicken Jan 18, 2024
6c82117
routerのパスを変更
mesichicken Jan 18, 2024
6f0daf4
apiのパスを修正
mesichicken Jan 20, 2024
14b9391
Merge branch 'feature/default-post-target-detect-from-path' into deve…
mesichicken Jan 20, 2024
af3a503
Merge branch 'feature/post-channel-everywhere' into develop_test
mesichicken Jan 20, 2024
4910f31
デグレの修正
mesichicken Jan 22, 2024
e011875
Merge branch 'develop' of github.com:mesichicken/misskey into feature…
mesichicken Jan 23, 2024
252f517
フォームにチャンネルの色情報を表示できるようにした
mesichicken Jan 23, 2024
b5a77e4
Merge branch 'develop' of github.com:mesichicken/misskey into develop…
mesichicken Jan 23, 2024
5ec94f9
Merge branch 'feature/default-post-target-detect-from-path' into deve…
mesichicken Jan 23, 2024
a561e9f
コメントが追加された
mesichicken Jan 23, 2024
986b17a
Merge branch 'develop' into feature/default-post-target-detect-from-path
mesichicken Jan 25, 2024
ef656ae
バグ修正
mesichicken Jan 29, 2024
72672dd
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih Mar 4, 2024
ddeab53
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih Mar 5, 2024
9df4fc0
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih May 21, 2024
b913011
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih May 21, 2024
65f63c0
fix
kakkokari-gtyih May 21, 2024
c0dced3
tweak
kakkokari-gtyih May 21, 2024
4e84285
tweak style
kakkokari-gtyih May 21, 2024
1aa41cb
tweak design
kakkokari-gtyih May 21, 2024
9377e39
tweak design
kakkokari-gtyih May 21, 2024
4e48ddc
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih May 21, 2024
7e0f6b9
Update Changelog
kakkokari-gtyih May 21, 2024
7dfa0e9
typo
kakkokari-gtyih May 21, 2024
702621d
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih May 22, 2024
250ed25
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih May 23, 2024
ae4c26b
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih May 27, 2024
121d92e
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih May 28, 2024
ffec314
use cache
kakkokari-gtyih May 28, 2024
c7f6133
refactor: 通常投稿ボタン用のハンドラを分離
kakkokari-gtyih May 28, 2024
1ca4d5f
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih Jul 4, 2024
93bca51
Update CHANGELOG.md
kakkokari-gtyih Jul 4, 2024
97aa84b
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih Sep 15, 2024
e9c1a62
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih Sep 21, 2024
605c2e0
Merge branch 'develop' into feature/default-post-target-detect-from-path
kakkokari-gtyih Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
- Enhance: アイコンデコレーション管理画面にプレビューを追加
- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
- Enhance: ScratchpadにUIインスペクターを追加
- Enhance: どこで投稿フォームを開いてもお気に入りに登録したチャンネルにノートできるように
- Enhance: チャンネルのページを開いている間はデフォルトの公開範囲がそのチャンネルになるように
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
- Fix: タッチ画面でレンジスライダーを操作するとツールチップが複数表示される問題を修正
Expand Down
12 changes: 12 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8591,6 +8591,18 @@ export interface Locale extends ILocale {
* 指定したユーザーのみに公開
*/
"specifiedDescription": string;
/**
* チャンネル
*/
"channel": string;
/**
* 選択したチャンネルに公開
*/
"channelDescription": string;
/**
* 選択中:{name}
*/
"channelSelected": ParameterizedString<"name">;
/**
* 連合なし
*/
Expand Down
3 changes: 3 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2260,6 +2260,9 @@ _visibility:
followersDescription: "自分のフォロワーのみに公開"
specified: "ダイレクト"
specifiedDescription: "指定したユーザーのみに公開"
channel: "チャンネル"
channelDescription: "選択したチャンネルに公開"
channelSelected: "選択中:{name}"
disableFederation: "連合なし"
disableFederationDescription: "他サーバーへの配信を行いません"

Expand Down
3 changes: 2 additions & 1 deletion packages/frontend/src/boot/main-boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mainRouter } from '@/router/main.js';
import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import { postButtonHandler } from '@/scripts/post-button-handler.js';

export async function mainBoot() {
const { isClientUpdated } = await common(() => createApp(
Expand Down Expand Up @@ -346,7 +347,7 @@ export async function mainBoot() {
const keymap = {
'p|n': () => {
if ($i == null) return;
post();
postButtonHandler(mainRouter.currentRef.value);
},
'd': () => {
defaultStore.set('darkMode', !defaultStore.state.darkMode);
Expand Down
132 changes: 76 additions & 56 deletions packages/frontend/src/components/MkPostForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ SPDX-License-Identifier: AGPL-3.0-only
</button>
</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">
<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>
<span v-if="visibility === 'specified'"><i class="ti ti-mail"></i></span>
<span :class="$style.headerRightButtonText">{{ i18n.ts._visibility[visibility] }}</span>
</button>
<button v-else class="_button" :class="[$style.headerRightItem, $style.visibility]" disabled>
<button ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility">
<template v-if="postChannel">
<span><i class="ti ti-device-tv"></i></span>
<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">
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
<span v-if="postChannel" :class="$style.headerRightButtonText">{{ postChannelName }}</span>
</template>
<template v-else>
<span v-if="actualVisibility === 'public'"><i class="ti ti-world"></i></span>
<span v-if="actualVisibility === 'home'"><i class="ti ti-home"></i></span>
<span v-if="actualVisibility === 'followers'"><i class="ti ti-lock"></i></span>
<span v-if="actualVisibility === 'specified'"><i class="ti ti-mail"></i></span>
<span :class="$style.headerRightButtonText">{{ i18n.ts._visibility[actualVisibility] }}</span>
</template>
</button>
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: actualLocalOnly }]" :disabled="postChannel != null || actualVisibility === 'specified'" @click="toggleLocalOnly">
<span v-if="!actualLocalOnly"><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">
Expand All @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkNoteSimple v-if="reply" :class="$style.targetNote" :note="reply"/>
<MkNoteSimple v-if="renote" :class="$style.targetNote" :note="renote"/>
<div v-if="quoteId" :class="$style.withQuote"><i class="ti ti-quote"></i> {{ i18n.ts.quoteAttached }}<button @click="quoteId = null"><i class="ti ti-x"></i></button></div>
<div v-if="visibility === 'specified'" :class="$style.toSpecified">
<div v-if="actualVisibility === 'specified'" :class="$style.toSpecified">
<span style="margin-right: 8px;">{{ i18n.ts.recipient }}</span>
<div :class="$style.visibleUsers">
<span v-for="u in visibleUsers" :key="u.id" :class="$style.visibleUser">
Expand All @@ -66,8 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]" :style="postChannel && postChannel.color ? `--channel-color: ${postChannel.color}` : undefined">
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
</div>
Expand Down Expand Up @@ -137,7 +136,7 @@ const modal = inject('modal');
const props = withDefaults(defineProps<{
reply?: Misskey.entities.Note;
renote?: Misskey.entities.Note;
channel?: Misskey.entities.Channel; // TODO
channel?: Misskey.entities.Channel;
mention?: Misskey.entities.User;
specified?: Misskey.entities.UserDetailed;
initialText?: string;
Expand Down Expand Up @@ -202,8 +201,22 @@ const imeText = ref('');
const showingOptions = ref(false);
const textAreaReadOnly = ref(false);

const postChannel = ref<Misskey.entities.Channel | null>(props.channel ?? null);
const postChannelName = computed<string>(() => postChannel.value?.name ?? '');

/**
* {@link localOnly}が持つ値にチャンネル選択有無を加味した値を計算する(チャンネル選択時は強制的にfalse)
* チャンネル選択有無を考慮する必要がある場面では{@link localOnly}ではなくこの値を使用する。
*/
const actualLocalOnly = computed<boolean>(() => postChannel.value ? true : localOnly.value);
/**
* {@link visibility}が持つ値にチャンネル選択有無を加味した値を計算する(チャンネル選択時は強制的にpublic)。
* チャンネル選択有無を考慮する必要がある場面では{@link actualVisibility}ではなくこの値を使用する。
*/
const actualVisibility = computed<typeof Misskey.noteVisibilities[number]>(() => postChannel.value ? 'public' : visibility.value);

const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
let key = postChannel.value ? `channel:${postChannel.value.id}` : '';

if (props.renote) {
key += `renote:${props.renote.id}`;
Expand All @@ -221,7 +234,7 @@ const placeholder = computed((): string => {
return i18n.ts._postForm.quotePlaceholder;
} else if (props.reply) {
return i18n.ts._postForm.replyPlaceholder;
} else if (props.channel) {
} else if (postChannel.value) {
return i18n.ts._postForm.channelPlaceholder;
} else {
const xs = [
Expand Down Expand Up @@ -272,7 +285,7 @@ watch(text, () => {
checkMissingMention();
}, { immediate: true });

watch(visibility, () => {
watch(actualVisibility, () => {
checkMissingMention();
}, { immediate: true });

Expand Down Expand Up @@ -316,11 +329,6 @@ if ($i.isSilenced && visibility.value === 'public') {
visibility.value = 'home';
}

if (props.channel) {
visibility.value = 'public';
localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す
}

// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) {
if (props.reply.visibility === 'home' && visibility.value === 'followers') {
Expand Down Expand Up @@ -372,7 +380,7 @@ function watchForDraft() {
}

function checkMissingMention() {
if (visibility.value === 'specified') {
if (actualVisibility.value === 'specified') {
const ast = mfm.parse(text.value);

for (const x of extractMentions(ast)) {
Expand Down Expand Up @@ -459,36 +467,31 @@ function upload(file: File, name?: string): void {
}

function setVisibility() {
if (props.channel) {
visibility.value = 'public';
localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す
return;
}

const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), {
currentVisibility: visibility.value,
currentVisibility: actualVisibility.value,
isSilenced: $i.isSilenced,
localOnly: localOnly.value,
// チャンネル→ダイレクトの選択変更ができなくなるので、チャンネル選択中の場合は意図的にfalseを渡すようにする
localOnly: postChannel.value ? false : actualLocalOnly.value,
src: visibilityButton.value,
currentChannel: postChannel.value,
...(props.reply ? { isReplyVisibilitySpecified: props.reply.visibility === 'specified' } : {}),
}, {
changeVisibility: v => {
visibility.value = v;
postChannel.value = null;
if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set('visibility', visibility.value);
}
},
changeChannel: channel => {
// computedで読み替えをするので、localOnlyとvisibilityの変更はしない
postChannel.value = channel;
},
closed: () => dispose(),
});
}

async function toggleLocalOnly() {
if (props.channel) {
visibility.value = 'public';
localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す
return;
}

const neverShowInfo = miLocalStorage.getItem('neverShowLocalOnlyInfo');

if (!localOnly.value && neverShowInfo !== 'true') {
Expand Down Expand Up @@ -716,6 +719,15 @@ function saveDraft() {
function deleteDraft() {
const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}');

if (postChannel.value) {
// draftKey.valueからchannel:${postChannel.value.id}部分を削除したのがpartialDraftKey
// 通常の投稿からチャンネルに切り替えて投稿した際に、通常の投稿の下書きが残ってしまい不自然な挙動になるのを防ぐ
const partialDraftKey = draftKey.value.replace(`channel:${postChannel.value.id}`, '');
if (draftData[partialDraftKey]) {
delete draftData[partialDraftKey];
}
}

delete draftData[draftKey.value];

miLocalStorage.setItem('drafts', JSON.stringify(draftData));
Expand Down Expand Up @@ -752,7 +764,8 @@ async function post(ev?: MouseEvent) {
text.value.includes('$[scale') ||
text.value.includes('$[position');

if (annoying && visibility.value === 'public') {
// チャンネル投稿時はサーバサイド側でpublicに変更されているため警告を出す意味がない
if (annoying && actualVisibility.value === 'public' && !postChannel.value) {
const { canceled, result } = await os.actions({
type: 'warning',
text: i18n.ts.thisPostMayBeAnnoying,
Expand Down Expand Up @@ -781,12 +794,12 @@ async function post(ev?: MouseEvent) {
fileIds: files.value.length > 0 ? files.value.map(f => f.id) : undefined,
replyId: props.reply ? props.reply.id : undefined,
renoteId: props.renote ? props.renote.id : quoteId.value ? quoteId.value : undefined,
channelId: props.channel ? props.channel.id : undefined,
channelId: postChannel.value ? postChannel.value.id : undefined,
poll: poll.value,
cw: useCw.value ? cw.value ?? '' : null,
localOnly: localOnly.value,
visibility: visibility.value,
visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined,
localOnly: actualLocalOnly.value,
visibility: actualVisibility.value,
visibleUserIds: actualVisibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined,
reactionAcceptance: reactionAcceptance.value,
};

Expand Down Expand Up @@ -1139,16 +1152,6 @@ defineExpose({
}
}

.colorBar {
position: absolute;
top: 0px;
left: 12px;
width: 5px;
height: 100% ;
border-radius: 999px;
pointer-events: none;
}

.submitInner {
padding: 0 12px;
line-height: 34px;
Expand Down Expand Up @@ -1277,6 +1280,18 @@ defineExpose({
width: 100%;
position: relative;

&::before {
content: '';
position: absolute;
top: 0;
left: 9.5px; // 9.5px + 5px + 9.5px = 24px
width: 5px;
height: 100%;
background: var(--channel-color, transparent);
border-radius: 2.5px;
pointer-events: none;
}

&.withCw {
padding-top: 8px;
}
Expand Down Expand Up @@ -1382,6 +1397,11 @@ defineExpose({
padding: 0 16px;
}

.textOuter::before {
left: 0;
border-radius: 0 5px 5px 0;
}

.text {
min-height: 80px;
}
Expand Down
Loading
Loading