1
0

Merge tag '2025.4.0' into merge/2025-03-24

# Conflicts:
#	.github/workflows/storybook.yml
#	locales/index.d.ts
#	package.json
#	packages/backend/src/models/json-schema/role.ts
#	packages/frontend/src/components/MkPageWindow.vue
#	packages/frontend/src/pages/admin/roles.editor.vue
#	packages/frontend/src/pages/admin/roles.vue
#	packages/frontend/src/pages/settings/preferences.vue
#	packages/frontend/src/pages/settings/privacy.vue
#	packages/frontend/src/pages/timeline.vue
#	packages/frontend/src/pref-migrate.ts
#	packages/frontend/src/ui/_common_/common.vue
#	packages/frontend/src/ui/deck.vue
#	packages/frontend/src/ui/universal.vue
#	packages/misskey-js/src/autogen/types.ts
This commit is contained in:
Hazelnoot
2025-04-13 13:04:57 -04:00
98 changed files with 1621 additions and 2087 deletions
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
<MkSwitch v-model="caseSensitive">{{ i18n.ts.caseSensitive }}</MkSwitch>
<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch>
<MkSwitch v-model="hideNotesInSensitiveChannel">{{ i18n.ts.hideNotesInSensitiveChannel }}</MkSwitch>
<MkSwitch v-model="excludeNotesInSensitiveChannel">{{ i18n.ts.excludeNotesInSensitiveChannel }}</MkSwitch>
</div>
<div :class="$style.actions">
<div class="_buttons">
@@ -54,6 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import type { DeepPartial } from '@/utility/merge.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -63,7 +64,6 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { deepMerge } from '@/utility/merge.js';
import type { DeepPartial } from '@/utility/merge.js';
type PartialAllowedAntenna = Omit<Misskey.entities.Antenna, 'id' | 'createdAt' | 'updatedAt'> & {
id?: string;
@@ -87,7 +87,7 @@ const initialAntenna = deepMerge<PartialAllowedAntenna>(props.antenna ?? {}, {
caseSensitive: false,
localOnly: false,
withFile: false,
hideNotesInSensitiveChannel: false,
excludeNotesInSensitiveChannel: false,
isActive: true,
hasUnreadNote: false,
notify: false,
@@ -110,7 +110,7 @@ const localOnly = ref<boolean>(initialAntenna.localOnly);
const excludeBots = ref<boolean>(initialAntenna.excludeBots);
const withReplies = ref<boolean>(initialAntenna.withReplies);
const withFile = ref<boolean>(initialAntenna.withFile);
const hideNotesInSensitiveChannel = ref<boolean>(initialAntenna.hideNotesInSensitiveChannel);
const excludeNotesInSensitiveChannel = ref<boolean>(initialAntenna.excludeNotesInSensitiveChannel);
const userLists = ref<Misskey.entities.UserList[] | null>(null);
watch(() => src.value, async () => {
@@ -127,7 +127,7 @@ async function saveAntenna() {
excludeBots: excludeBots.value,
withReplies: withReplies.value,
withFile: withFile.value,
hideNotesInSensitiveChannel: hideNotesInSensitiveChannel.value,
excludeNotesInSensitiveChannel: excludeNotesInSensitiveChannel.value,
caseSensitive: caseSensitive.value,
localOnly: localOnly.value,
users: users.value.trim().split('\n').map(x => x.trim()),
+1 -1
View File
@@ -473,7 +473,7 @@ onBeforeUnmount(() => {
}
}
&:not(.widthSpecified) {
&:not(.asDrawer):not(.widthSpecified) {
> .menu {
max-width: 400px;
}
@@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
[$style.t_login]: notification.type === 'login',
[$style.t_createToken]: notification.type === 'createToken',
[$style.t_chatRoomInvitationReceived]: notification.type === 'chatRoomInvitationReceived',
[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
[$style.t_pollEnded]: notification.type === 'edited',
[$style.t_roleAssigned]: notification.type === 'scheduledNoteFailed',
@@ -424,6 +425,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none;
}
.t_chatRoomInvitationReceived {
padding: 3px;
background: var(--eventOther);
pointer-events: none;
}
.tail {
flex: 1;
min-width: 0;
@@ -113,7 +113,7 @@ windowRouter.addListener('change', ctx => {
windowRouter.init();
provide(DI.router, windowRouter);
provide('inAppSearchMarkerId', searchMarkerId);
provide(DI.inAppSearchMarkerId, searchMarkerId);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
@@ -121,7 +121,7 @@ provideMetadataReceiver((metadataGetter) => {
provideReactiveMetadata(pageMetadata);
provide('shouldOmitHeaderTitle', true);
provide('shouldHeaderThin', true);
provide('forceSpacerMin', true);
provide(DI.forceSpacerMin, true);
provide('shouldBackButton', false);
const contextmenu = computed(() => ([{
@@ -93,11 +93,11 @@ export type SuperMenuDef = {
</script>
<script lang="ts" setup>
import { useTemplateRef, ref, watch, nextTick } from 'vue';
import { useTemplateRef, ref, watch, nextTick, computed } from 'vue';
import { getScrollContainer } from '@@/js/scroll.js';
import type { SearchIndexItem } from '@/utility/settings-search-index.js';
import MkInput from '@/components/MkInput.vue';
import { i18n } from '@/i18n.js';
import { getScrollContainer } from '@@/js/scroll.js';
import { useRouter } from '@/router.js';
import { initIntlString, compareStringIncludes } from '@/utility/intl-string.js';
@@ -124,6 +124,7 @@ const searchResult = ref<{
isRoot: boolean;
parentLabels: string[];
}[]>([]);
const searchIndexItemByIdComputed = computed(() => props.searchIndex && new Map<string, SearchIndexItem>(props.searchIndex.map(i => [i.id, i])));
watch(searchQuery, (value) => {
rawSearchQuery.value = value;
@@ -137,32 +138,41 @@ watch(rawSearchQuery, (value) => {
return;
}
const dive = (items: SearchIndexItem[], parents: SearchIndexItem[] = []) => {
for (const item of items) {
const matched = (
const searchIndexItemById = searchIndexItemByIdComputed.value;
if (searchIndexItemById != null) {
const addSearchResult = (item: SearchIndexItem) => {
let path: string | undefined = item.path;
let icon: string | undefined = item.icon;
const parentLabels: string[] = [];
for (let current = searchIndexItemById.get(item.parentId ?? '');
current != null;
current = searchIndexItemById.get(current.parentId ?? '')) {
path ??= current.path;
icon ??= current.icon;
parentLabels.push(current.label);
}
if (_DEV_ && path == null) throw new Error('path is null for ' + item.id);
searchResult.value.push({
id: item.id,
path: path ?? '/', // never gets `/`
label: item.label,
parentLabels: parentLabels.toReversed(),
icon,
isRoot: item.parentId == null,
});
};
for (const item of searchIndexItemById.values()) {
if (
compareStringIncludes(item.label, value) ||
item.keywords.some((x) => compareStringIncludes(x, value))
);
if (matched) {
searchResult.value.push({
id: item.id,
path: item.path ?? parents.find((x) => x.path != null)?.path ?? '/', // never gets `/`
label: item.label,
parentLabels: parents.map((x) => x.label).toReversed(),
icon: item.icon ?? parents.find((x) => x.icon != null)?.icon,
isRoot: parents.length === 0,
});
}
if (item.children) {
dive(item.children, [item, ...parents]);
) {
addSearchResult(item);
}
}
};
if (props.searchIndex) {
dive(props.searchIndex);
}
});
@@ -22,7 +22,7 @@ const modal = useTemplateRef('modal');
const props = defineProps<{
success: boolean;
showing: boolean;
text?: string;
text?: string | null;
}>();
const emit = defineEmits<{
@@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { inject } from 'vue';
import { deviceKind } from '@/utility/device-kind.js';
import { DI } from '@/di.js';
const props = withDefaults(defineProps<{
contentMax?: number | null;
@@ -25,7 +26,7 @@ const props = withDefaults(defineProps<{
marginMax: 24,
});
const forceSpacerMin = inject('forceSpacerMin', false) || deviceKind === 'smartphone';
const forceSpacerMin = inject(DI.forceSpacerMin, false) || deviceKind === 'smartphone';
</script>
<style lang="scss" module>
@@ -0,0 +1,14 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<slot></slot>
</template>
<script lang="ts" setup>
</script>
<style lang="scss" module>
</style>
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div ref="root" :class="[$style.root, { [$style.highlighted]: highlighted }]">
<slot></slot>
<slot :isParentOfTarget="isParentOfTarget"></slot>
</div>
</template>
@@ -21,7 +21,7 @@ import {
useTemplateRef,
inject,
} from 'vue';
import type { Ref } from 'vue';
import { DI } from '@/di.js';
const props = defineProps<{
markerId?: string;
@@ -36,12 +36,13 @@ const rootEl = useTemplateRef('root');
const rootElMutationObserver = new MutationObserver(() => {
checkChildren();
});
const injectedSearchMarkerId = inject<Ref<string | null> | null>('inAppSearchMarkerId', null);
const injectedSearchMarkerId = inject(DI.inAppSearchMarkerId, null);
const searchMarkerId = computed(() => injectedSearchMarkerId?.value ?? window.location.hash.slice(1));
const highlighted = ref(props.markerId === searchMarkerId.value);
const isParentOfTarget = computed(() => props.children?.includes(searchMarkerId.value));
function checkChildren() {
if (props.children?.includes(searchMarkerId.value)) {
if (isParentOfTarget.value) {
const el = window.document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`);
highlighted.value = el == null;
}
@@ -105,8 +106,8 @@ onBeforeUnmount(dispose);
@keyframes blink {
0%, 100% {
background: color(from var(--MI_THEME-accent) srgb r g b / 0.05);
border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.7);
background: color(from var(--MI_THEME-accent) srgb r g b / 0.1);
border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.75);
}
50% {
background: transparent;
@@ -30,6 +30,7 @@ import PageWithAnimBg from './global/PageWithAnimBg.vue';
import SearchMarker from './global/SearchMarker.vue';
import SearchLabel from './global/SearchLabel.vue';
import SearchKeyword from './global/SearchKeyword.vue';
import SearchIcon from './global/SearchIcon.vue';
import type { App } from 'vue';
@@ -67,6 +68,7 @@ export const components = {
SearchMarker: SearchMarker,
SearchLabel: SearchLabel,
SearchKeyword: SearchKeyword,
SearchIcon: SearchIcon,
};
declare module '@vue/runtime-core' {
@@ -98,5 +100,6 @@ declare module '@vue/runtime-core' {
SearchMarker: typeof SearchMarker;
SearchLabel: typeof SearchLabel;
SearchKeyword: typeof SearchKeyword;
SearchIcon: typeof SearchIcon;
}
}