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:
@@ -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()),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user