Merge branch 'develop' into merge/2024-02-03

# Conflicts:
#	locales/index.d.ts
#	packages/backend/src/core/entities/UserEntityService.ts
#	packages/frontend/src/_dev_boot_.ts
#	packages/misskey-js/src/autogen/types.ts
#	sharkey-locales/en-US.yml
This commit is contained in:
Hazelnoot 2025-02-07 11:54:29 -05:00
commit f36029f795
24 changed files with 512 additions and 35 deletions

View file

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button
class="_button"
:class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing || hasPendingFollowRequestFromYou, [$style.full]: full, [$style.large]: large }]"
:disabled="wait"
:disabled="wait || disabled"
@click="onClick"
>
<template v-if="!wait">
@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
import * as os from '@/os.js';
@ -51,13 +51,16 @@ const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed,
full?: boolean,
large?: boolean,
disabled?: boolean,
}>(), {
full: false,
large: false,
disabled: false,
});
const emit = defineEmits<{
(_: 'update:user', value: Misskey.entities.UserDetailed): void
(_: 'update:user', value: Misskey.entities.UserDetailed): void,
(_: 'update:wait', value: boolean): void,
}>();
const isFollowing = ref(props.user.isFollowing);
@ -65,6 +68,9 @@ const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFro
const wait = ref(false);
const connection = useStream().useChannel('main');
// Emit the "wait" status so external components can synchronize state
watch(wait, value => emit('update:wait', value));
if (props.user.isFollowing == null && $i) {
misskeyApi('users/show', {
userId: props.user.id,

View file

@ -122,6 +122,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<template v-else-if="notification.type === 'follow'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
<div v-if="full" :class="$style.followRequestCommands">
<MkFollowButton v-if="userDetailed" :class="$style.followCommandButton" :user="userDetailed" :transparent="false" :full="false"/>
</div>
</template>
<template v-else-if="notification.type === 'followRequestAccepted'">
<div :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</div>
@ -136,6 +139,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="full && !followRequestDone" :class="$style.followRequestCommands">
<MkButton :class="$style.followRequestCommandButton" rounded primary @click="acceptFollowRequest()"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
<MkFollowButton v-if="userDetailed" :class="$style.followCommandButton" :user="userDetailed" :transparent="false" :full="false"/>
</div>
</template>
<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>
@ -179,8 +183,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Ref, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { UserDetailed } from 'misskey-js/autogen/models.js';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue';
import { getNoteSummary } from '@/scripts/get-note-summary.js';
@ -190,6 +195,7 @@ import { i18n } from '@/i18n.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { signinRequired } from '@/account.js';
import { infoImageUrl } from '@/instance.js';
import MkFollowButton from '@/components/MkFollowButton.vue';
const $i = signinRequired();
@ -202,6 +208,26 @@ const props = withDefaults(defineProps<{
full: false,
});
const userDetailed: Ref<UserDetailed | null> = ref(null);
// watch() is required because computed() doesn't support async.
watch(props, async () => {
const type = props.notification.type;
// To avoid extra lookups, only do the query when it actually matters.
if (type === 'follow' || type === 'receiveFollowRequest') {
const user = await misskeyApi('users/show', {
userId: props.notification.userId,
});
userDetailed.value = user;
followRequestDone.value = !user.hasPendingFollowRequestToYou;
} else {
userDetailed.value = null;
followRequestDone.value = false;
}
}, { immediate: true });
type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
const exportEntityName = {
@ -216,7 +242,7 @@ const exportEntityName = {
userList: i18n.ts.lists,
} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
const followRequestDone = ref(false);
const followRequestDone = ref(true);
const acceptFollowRequest = () => {
if (!('user' in props.notification)) return;
@ -434,13 +460,24 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
.followRequestCommands {
display: flex;
gap: 8px;
max-width: 300px;
margin-top: 8px;
width: 100%;
}
.followRequestCommandButton {
max-width: 175px;
width: 100%;
}
.flexSpacer {
flex: 1;
}
.followCommandButton {
margin-left: auto;
flex-grow: 0;
flex-shrink: 0;
}
.reactionsItem {
display: inline-block;
position: relative;

View file

@ -366,6 +366,29 @@ if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
cw.value = props.reply.cw;
}
// apply default CW
if ($i.defaultCW) {
useCw.value = true;
if (!cw.value || $i.defaultCWPriority === 'default') {
cw.value = $i.defaultCW;
} else if ($i.defaultCWPriority !== 'parent') {
// This is a fancy way of simulating /\bsearch\b/ without a regular expression.
// We're checking to see whether the default CW appears inside the existing CW, but *only* if there's word boundaries.
const parts = cw.value.split($i.defaultCW);
const hasExistingDefaultCW = parts.length === 2 && !/\w$/.test(parts[0]) && !/^\w/.test(parts[1]);
if (!hasExistingDefaultCW) {
// We need to merge the CWs
if ($i.defaultCWPriority === 'defaultParent') {
cw.value = `${$i.defaultCW}, ${cw.value}`;
} else if ($i.defaultCWPriority === 'parentDefault') {
cw.value = `${cw.value}, ${$i.defaultCW}`;
}
}
}
// else { do nothing, because existing CW takes priority. }
}
function watchForDraft() {
watch(text, () => saveDraft());
watch(useCw, () => saveDraft());

View file

@ -358,6 +358,10 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]);
}
}
case 'group': { // this is mostly a hack for the insides of `ruby`
style = '';
break;
}
case 'unixtime': {
const child = token.children[0];
const unixtime = parseInt(child.type === 'text' ? child.props.text : '');