1
1
mirror of https://github.com/kokonect-link/cherrypick synced 2024-11-27 22:38:34 +09:00

Merge pull request #400

* Feat (Frontend): DefaultStore: Add 'renoteVisibilitySelection'

* Impl (Frontend): Add 'addDividersBetweenMenuSections' utility functio…

* Fix (Frontend): Fix 'Prefer named exports' warn

* Refactor (Frontend): use addDividersBetweenMenuSections instead of us…

* Impl (Frontend): Add 'visibilityRenoteItems' to support new feature

* Merge branch 'develop' into renote-visibility

* Impl (Frontend): Add getQuoteMenu to separate home quote, channel quote

* Locale (Frontend): Update ko-KR, en-US

* Impl (Frontend): Apply changes to MkSubNoteContent also

* Fix (Frontend): renote menu condition

* Locale (Frontend): reuse exist string

* Fix mistake

* Fix mistake (...)

* Fix (Frontend): change button order
This commit is contained in:
Qwreey 2023-12-18 17:46:11 +09:00 committed by GitHub
parent 214047c76b
commit 3b2ffa15ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 214 additions and 48 deletions

View File

@ -1277,6 +1277,7 @@ additionalPermissionsForFlash: "Allow to add permission to Play"
thisFlashRequiresTheFollowingPermissions: "This Play requires the following permissions"
doYouWantToAllowThisPlayToAccessYourAccount: "Do you want to allow this Play to access your account?"
translateProfile: "Translate profile"
showRenoteVisibilitySelector: "Show renote visibility selector"
_nsfwOpenBehavior:
click: "Click to open"
doubleClick: "Double click to open"

1
locales/index.d.ts vendored
View File

@ -1289,6 +1289,7 @@ export interface Locale {
"thisFlashRequiresTheFollowingPermissions": string;
"doYouWantToAllowThisPlayToAccessYourAccount": string;
"translateProfile": string;
"showRenoteVisibilitySelector": string;
"_nsfwOpenBehavior": {
"click": string;
"doubleClick": string;

View File

@ -1286,6 +1286,7 @@ additionalPermissionsForFlash: "Playへの追加許可"
thisFlashRequiresTheFollowingPermissions: "このPlayは以下の権限を要求しています"
doYouWantToAllowThisPlayToAccessYourAccount: "このPlayによるアカウントへのアクセスを許可しますか"
translateProfile: "プロフィールを翻訳する"
showRenoteVisibilitySelector: "TRANSLATME-showRenoteVisibilitySelector"
_nsfwOpenBehavior:
click: "タップして開く"

View File

@ -1277,6 +1277,7 @@ additionalPermissionsForFlash: "Play에 대한 추가 권한"
thisFlashRequiresTheFollowingPermissions: "이 Play는 다음 권한을 요구해요"
doYouWantToAllowThisPlayToAccessYourAccount: "이 Play가 계정에 접근하도록 허용할까요?"
translateProfile: "프로필 번역하기"
showRenoteVisibilitySelector: "리노트 공개범위 옵션 표시"
_nsfwOpenBehavior:
click: "탭하여 열기"
doubleClick: "두 번 탭하여 열기"

View File

@ -146,7 +146,7 @@ SPDX-License-Identifier: AGPL-3.0-only
v-tooltip="i18n.ts.renote"
:class="$style.footerButton"
class="_button"
@click.stop="defaultStore.state.renoteQuoteButtonSeparation ? renoteOnly() : renote()"
@click.stop="(defaultStore.state.renoteQuoteButtonSeparation && !defaultStore.state.renoteVisibilitySelection && !appearNote.channel) || (defaultStore.state.renoteQuoteButtonSeparation && appearNote.channel && !appearNote.channel.allowRenoteToExternal) || (defaultStore.state.renoteQuoteButtonSeparation && appearNote.visibility === 'followers') ? renoteOnly() : renote()"
>
<i class="ti ti-repeat"></i>
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p>
@ -164,7 +164,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-if="appearNote.myReaction != null && appearNote.reactionAcceptance == 'likeOnly'" ref="reactButton" v-vibrate="defaultStore.state.vibrateSystem ? [30, 50, 50] : []" v-tooltip="i18n.ts.removeReaction" :class="$style.footerButton" class="_button" @click.stop="undoReact(appearNote)">
<i class="ti ti-heart-minus"></i>
</button>
<button v-if="canRenote && defaultStore.state.renoteQuoteButtonSeparation" v-vibrate="defaultStore.state.vibrateSystem ? 5 : []" v-tooltip="i18n.ts.quote" class="_button" :class="$style.footerButton" @click.stop="quote()">
<button v-if="canRenote && defaultStore.state.renoteQuoteButtonSeparation" ref="quoteButton" v-vibrate="defaultStore.state.vibrateSystem ? 5 : []" v-tooltip="i18n.ts.quote" class="_button" :class="$style.footerButton" @click.stop="quote()">
<i class="ti ti-quote"></i>
</button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" v-vibrate="defaultStore.state.vibrateSystem ? 5 : []" v-tooltip="i18n.ts.clip" :class="$style.footerButton" class="_button" @click.stop="clip()">
@ -222,7 +222,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu, getRenoteOnly } from '@/scripts/get-note-menu.js';
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu, getRenoteOnly, getQuoteMenu } from '@/scripts/get-note-menu.js';
import { useNoteCapture } from '@/scripts/use-note-capture.js';
import { deepClone } from '@/scripts/clone.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
@ -293,6 +293,7 @@ const isRenote = (
const el = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const quoteButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
@ -418,19 +419,27 @@ function quote(viaKeyboard = false): void {
return;
}
if (appearNote.value.channel) {
if (appearNote.value.channel.allowRenoteToExternal) {
const { menu } = getQuoteMenu({ note: note.value, mock: props.mock });
os.popupMenu(menu, quoteButton.value, {
viaKeyboard,
});
} else {
os.post({
renote: appearNote.value,
channel: appearNote.value.channel,
animation: !viaKeyboard,
}, () => {
focus();
});
}
} else {
os.post({
renote: appearNote.value,
channel: appearNote.value.channel,
animation: !viaKeyboard,
}, () => {
focus();
});
}
os.post({
renote: appearNote.value,
}, () => {
focus();
});
}
function reply(viaKeyboard = false): void {

View File

@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
v-tooltip="i18n.ts.renote"
:class="$style.footerButton"
class="_button"
@click.stop="defaultStore.state.renoteQuoteButtonSeparation ? renoteOnly() : renote()"
@click.stop="(defaultStore.state.renoteQuoteButtonSeparation && !defaultStore.state.renoteVisibilitySelection && !note.channel) || (defaultStore.state.renoteQuoteButtonSeparation && note.channel && !note.channel.allowRenoteToExternal) || (defaultStore.state.renoteQuoteButtonSeparation && note.visibility === 'followers') ? renoteOnly() : renote()"
>
<i class="ti ti-repeat"></i>
<p v-if="note.renoteCount > 0" :class="$style.footerButtonCount">{{ note.renoteCount }}</p>
@ -98,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-if="note.myReaction != null && note.reactionAcceptance == 'likeOnly'" ref="reactButton" v-vibrate="defaultStore.state.vibrateSystem ? [30, 50, 50] : []" v-tooltip="i18n.ts.removeReaction" :class="$style.footerButton" class="_button" @click.stop="undoReact(note)">
<i class="ti ti-heart-minus"></i>
</button>
<button v-if="canRenote && defaultStore.state.renoteQuoteButtonSeparation" v-vibrate="defaultStore.state.vibrateSystem ? 5 : []" v-tooltip="i18n.ts.quote" class="_button" :class="$style.footerButton" @click.stop="quote()">
<button v-if="canRenote && defaultStore.state.renoteQuoteButtonSeparation" ref="quoteButton" v-vibrate="defaultStore.state.vibrateSystem ? 5 : []" v-tooltip="i18n.ts.quote" class="_button" :class="$style.footerButton" @click.stop="quote()">
<i class="ti ti-quote"></i>
</button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" v-vibrate="defaultStore.state.vibrateSystem ? 5 : []" v-tooltip="i18n.ts.clip" :class="$style.footerButton" class="_button" @click.stop="clip()">
@ -136,7 +136,7 @@ import { notePage } from '@/filters/note.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import { pleaseLogin } from '@/scripts/please-login.js';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { getNoteClipMenu, getNoteMenu, getRenoteMenu, getRenoteOnly } from '@/scripts/get-note-menu.js';
import { getNoteClipMenu, getNoteMenu, getRenoteMenu, getRenoteOnly, getQuoteMenu } from '@/scripts/get-note-menu.js';
import { deepClone } from '@/scripts/clone.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
import { claimAchievement } from '@/scripts/achievements.js';
@ -165,6 +165,7 @@ const note = ref(deepClone(props.note));
const el = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const quoteButton = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const heartReactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
@ -248,19 +249,27 @@ function quote(viaKeyboard = false): void {
return;
}
if (props.note.channel) {
if (props.note.channel.allowRenoteToExternal) {
const { menu } = getQuoteMenu({ note: note.value, mock: props.mock });
os.popupMenu(menu, quoteButton.value, {
viaKeyboard,
});
} else {
os.post({
renote: props.note,
channel: props.note.channel,
animation: !viaKeyboard,
}, () => {
focus();
});
}
} else {
os.post({
renote: props.note,
channel: props.note.channel,
animation: !viaKeyboard,
}, () => {
focus();
});
}
os.post({
renote: props.note,
}, () => {
focus();
});
}
function reply(viaKeyboard = false): void {

View File

@ -74,6 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="infoButtonForNoteActionsEnabled">{{ i18n.ts.infoButtonForNoteActions }}<template #caption>{{ i18n.ts.infoButtonForNoteActionsDescription }}</template> <span class="_beta">CherryPick</span></MkSwitch>
<MkSwitch v-model="showReplyInNotification">{{ i18n.ts.showReplyInNotification }} <span class="_beta">CherryPick</span></MkSwitch>
<MkSwitch v-model="renoteQuoteButtonSeparation">{{ i18n.ts.renoteQuoteButtonSeparation }} <span class="_beta">CherryPick</span></MkSwitch>
<MkSwitch v-model="renoteVisibilitySelection">{{ i18n.ts.showRenoteVisibilitySelector }} <span class="_beta">CherryPick</span></MkSwitch>
<MkSwitch v-model="showFixedPostFormInReplies">{{ i18n.ts.showFixedPostFormInReplies }}<template #caption>{{ i18n.ts.showFixedPostFormInRepliesDescription }}</template> <span class="_beta">CherryPick</span></MkSwitch>
<MkSwitch v-model="allMediaNoteCollapse">{{ i18n.ts.allMediaNoteCollapse }} <span class="_beta">CherryPick</span></MkSwitch>
</div>
@ -426,6 +427,7 @@ const showFixedPostFormInReplies = computed(defaultStore.makeGetterSetter('showF
const showingAnimatedImages = computed(defaultStore.makeGetterSetter('showingAnimatedImages'));
const allMediaNoteCollapse = computed(defaultStore.makeGetterSetter('allMediaNoteCollapse'));
const nsfwOpenBehavior = computed(defaultStore.makeGetterSetter('nsfwOpenBehavior'));
const renoteVisibilitySelection = computed(defaultStore.makeGetterSetter('renoteVisibilitySelection'));
watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string);

View File

@ -0,0 +1,17 @@
import { MenuItem } from '@/types/menu.js';
// Add dividers between menu sections. if some menu section has menu item
export function addDividersBetweenMenuSections(...sections:MenuItem[][]): MenuItem[] {
const result:MenuItem[] = [];
let needDivider = false;
for (const section of sections) {
if (section.length !== 0) {
if (needDivider) result.push({ type: 'divider' });
needDivider = true;
result.push(...section);
}
}
return result;
}

View File

@ -19,6 +19,8 @@ import { clipsCache } from '@/cache.js';
import { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { isSupportShare } from '@/scripts/navigator.js';
import { addDividersBetweenMenuSections } from '@/scripts/add-dividers-between-menu-sections.js';
import { ap } from 'vitest/dist/reporters-5f784f42.js';
export async function getNoteClipMenu(props: {
note: Misskey.entities.Note;
@ -548,6 +550,49 @@ function smallerVisibility(a: Visibility | string, b: Visibility | string): Visi
return 'public';
}
export function getQuoteMenu(props: {
note: Misskey.entities.Note,
mock?: boolean;
}) {
const isRenote = (
props.note.renote != null &&
props.note.text == null &&
props.note.fileIds.length === 0 &&
props.note.poll == null
);
const menu: MenuItem[] = [];
const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note;
if (!appearNote.channel || appearNote.channel.allowRenoteToExternal) {
menu.push({
text: i18n.ts.quote,
icon: 'ti ti-quote',
action: () => {
os.post({
renote: appearNote,
});
},
});
}
if (appearNote.channel) {
menu.push({
text: i18n.ts.inChannelQuote,
icon: 'ti ti-device-tv',
action: () => {
if (!props.mock) {
os.post({
renote: appearNote,
channel: appearNote.channel,
});
}
},
});
}
return { menu };
}
export function getRenoteMenu(props: {
note: Misskey.entities.Note;
renoteButton: Ref<HTMLElement>;
@ -564,9 +609,11 @@ export function getRenoteMenu(props: {
const channelRenoteItems: MenuItem[] = [];
const normalRenoteItems: MenuItem[] = [];
const visibilityRenoteItems: MenuItem[] = [];
// Add channel renote/quote buttons
if (appearNote.channel) {
channelRenoteItems.push(...[{
const channelRenoteButton = {
text: i18n.ts.inChannelRenote,
icon: 'ti ti-repeat',
action: () => {
@ -587,22 +634,32 @@ export function getRenoteMenu(props: {
});
}
},
}, {
text: i18n.ts.inChannelQuote,
icon: 'ti ti-quote',
action: () => {
if (!props.mock) {
os.post({
renote: appearNote,
channel: appearNote.channel,
});
}
},
}]);
};
if (defaultStore.state.renoteQuoteButtonSeparation) {
normalRenoteItems.unshift(channelRenoteButton);
} else {
channelRenoteItems.push(channelRenoteButton);
}
// Add quote button if quote button is not separated
if (!defaultStore.state.renoteQuoteButtonSeparation) {
channelRenoteItems.push({
text: i18n.ts.inChannelQuote,
icon: 'ti ti-quote',
action: () => {
if (!props.mock) {
os.post({
renote: appearNote,
channel: appearNote.channel,
});
}
},
});
}
}
if (!appearNote.channel || appearNote.channel.allowRenoteToExternal) {
normalRenoteItems.push(...[{
normalRenoteItems.push({
text: i18n.ts.renote,
icon: 'ti ti-repeat',
action: () => {
@ -633,22 +690,86 @@ export function getRenoteMenu(props: {
});
}
},
}, (props.mock) ? undefined : {
text: i18n.ts.quote,
icon: 'ti ti-quote',
action: () => {
os.post({
renote: appearNote,
});
},
}]);
});
// Add quote button if quote button is not separated
if (!props.mock && !defaultStore.state.renoteQuoteButtonSeparation) {
normalRenoteItems.push({
text: i18n.ts.quote,
icon: 'ti ti-quote',
action: () => {
os.post({
renote: appearNote,
});
},
});
}
}
const renoteItems = [
...normalRenoteItems,
...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] : [],
...channelRenoteItems,
];
// Add visibility section
if (
defaultStore.state.renoteVisibilitySelection &&
!['followers', 'specified'].includes(appearNote.visibility)
&& (!appearNote.channel || appearNote.channel.allowRenoteToExternal)
) {
// renote to public
if (appearNote.visibility === 'public') {
visibilityRenoteItems.push({
text: `${i18n.ts.renote} (${i18n.ts._visibility.public})`,
icon: 'ti ti-world',
action: () => {
const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
os.api('notes/create', {
localOnly,
visibility: 'public',
renoteId: appearNote.id,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
});
},
});
}
// renote to home
if (['home', 'public'].includes(appearNote.visibility)) {
visibilityRenoteItems.push({
text: `${i18n.ts.renote} (${i18n.ts._visibility.home})`,
icon: 'ti ti-home',
action: () => {
const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
os.api('notes/create', {
localOnly,
visibility: 'home',
renoteId: appearNote.id,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
});
},
});
}
// renote to followers
visibilityRenoteItems.push({
text: `${i18n.ts.renote} (${i18n.ts._visibility.followers})`,
icon: 'ti ti-lock',
action: () => {
const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
os.api('notes/create', {
localOnly,
visibility: 'followers',
renoteId: appearNote.id,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
});
},
});
}
const renoteItems = addDividersBetweenMenuSections(
normalRenoteItems,
channelRenoteItems,
visibilityRenoteItems,
);
return {
menu: renoteItems,

View File

@ -537,6 +537,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: true,
},
renoteVisibilitySelection: {
where: 'device',
default: true,
},
showFixedPostFormInReplies: {
where: 'device',
default: true,