Merge branch 'misskey-develop' into merge/2025-03-24

# Conflicts:
#	.github/workflows/api-misskey-js.yml
#	.github/workflows/changelog-check.yml
#	.github/workflows/check-misskey-js-autogen.yml
#	.github/workflows/get-api-diff.yml
#	.github/workflows/lint.yml
#	.github/workflows/locale.yml
#	.github/workflows/on-release-created.yml
#	.github/workflows/storybook.yml
#	.github/workflows/test-backend.yml
#	.github/workflows/test-federation.yml
#	.github/workflows/test-frontend.yml
#	.github/workflows/test-misskey-js.yml
#	.github/workflows/test-production.yml
#	.github/workflows/validate-api-json.yml
#	package.json
#	packages/backend/package.json
#	packages/backend/src/server/api/ApiCallService.ts
#	packages/backend/src/server/api/endpoints/drive/files/create.ts
#	packages/frontend-shared/js/url.ts
#	packages/frontend/package.json
#	packages/frontend/src/components/MkFileCaptionEditWindow.vue
#	packages/frontend/src/components/MkInfo.vue
#	packages/frontend/src/components/MkLink.vue
#	packages/frontend/src/components/MkNote.vue
#	packages/frontend/src/components/MkNotes.vue
#	packages/frontend/src/components/MkPageWindow.vue
#	packages/frontend/src/components/MkReactionsViewer.vue
#	packages/frontend/src/components/MkTimeline.vue
#	packages/frontend/src/components/MkUrlPreview.vue
#	packages/frontend/src/components/MkUserPopup.vue
#	packages/frontend/src/components/global/MkPageHeader.vue
#	packages/frontend/src/components/global/MkUrl.vue
#	packages/frontend/src/components/global/PageWithHeader.vue
#	packages/frontend/src/pages/about-misskey.vue
#	packages/frontend/src/pages/announcements.vue
#	packages/frontend/src/pages/antenna-timeline.vue
#	packages/frontend/src/pages/channel.vue
#	packages/frontend/src/pages/instance-info.vue
#	packages/frontend/src/pages/note.vue
#	packages/frontend/src/pages/page.vue
#	packages/frontend/src/pages/role.vue
#	packages/frontend/src/pages/tag.vue
#	packages/frontend/src/pages/timeline.vue
#	packages/frontend/src/pages/user-list-timeline.vue
#	packages/frontend/src/pages/user/followers.vue
#	packages/frontend/src/pages/user/following.vue
#	packages/frontend/src/pages/user/home.vue
#	packages/frontend/src/pages/user/index.vue
#	packages/frontend/src/ui/deck.vue
#	packages/misskey-js/generator/package.json
#	pnpm-lock.yaml
#	scripts/changelog-checker/package-lock.json
#	scripts/changelog-checker/package.json
This commit is contained in:
Hazelnoot 2025-04-29 15:37:43 -04:00
commit 9c301fa5aa
255 changed files with 4773 additions and 4085 deletions

View file

@ -421,7 +421,7 @@ function getPointerEvents() {
#devTicker {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 2147483647;
color: #ff0;

View file

@ -79,10 +79,9 @@ watch(rootEl, () => {
.root {
position: relative;
z-index: 1;
padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
padding-bottom: env(safe-area-inset-bottom, 0px);
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
grid-gap: 8px;
width: 100%;
box-sizing: border-box;
background: var(--MI_THEME-navBg);
@ -91,6 +90,16 @@ watch(rootEl, () => {
}
.item {
padding: 12px 0;
&:first-child {
padding-left: 12px;
}
&:last-child {
padding-right: 12px;
}
&.post {
.itemInner {
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
@ -112,7 +121,7 @@ watch(rootEl, () => {
padding: 0;
aspect-ratio: 1;
width: 100%;
max-width: 45px;
max-width: 42px;
margin: auto;
align-content: center;
border-radius: 100%;

View file

@ -121,6 +121,7 @@ function more() {
display: inline-block;
width: 38px;
aspect-ratio: 1;
border-radius: 8px;
}
.wideInstanceIcon {

View file

@ -7,7 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="[$style.root, { [$style.iconOnly]: iconOnly }]">
<div :class="$style.body">
<div :class="$style.top">
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
<img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl || '/favicon.ico'" alt="" :class="instance.sidebarLogoUrl && !iconOnly ? $style.wideInstanceIcon : $style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
</button>
@ -183,12 +182,9 @@ function menuEdit() {
}
.body {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
position: relative;
width: var(--nav-icon-only-width);
height: 100dvh;
height: 100%;
box-sizing: border-box;
overflow: auto;
overflow-x: clip;
@ -303,18 +299,6 @@ function menuEdit() {
backdrop-filter: var(--MI-blur, blur(8px));
}
.banner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center center;
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
}
.instance {
position: relative;
display: block;
@ -335,6 +319,7 @@ function menuEdit() {
display: inline-block;
width: 38px;
aspect-ratio: 1;
border-radius: 8px;
}
.wideInstanceIcon {
@ -566,6 +551,7 @@ function menuEdit() {
display: inline-block;
width: 30px;
aspect-ratio: 1;
border-radius: 8px;
}
.bottom {

View file

@ -0,0 +1,87 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root">
<div :class="$style.title">
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/>
<span :class="$style.instanceTitle">{{ instance.name ?? host }}</span>
</div>
<div :class="$style.controls">
<span :class="$style.left">
<button v-if="canBack" class="_button" :class="$style.button" @click="goBack"><i class="ti ti-arrow-left"></i></button>
</span>
<span :class="$style.right">
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { host } from '@@/js/config.js';
import { ref } from 'vue';
import { instance } from '@/instance.js';
import { prefer } from '@/preferences.js';
const canBack = ref(true);
function goBack() {
window.history.back();
}
</script>
<style lang="scss" module>
.root {
--height: 36px;
background: var(--MI_THEME-navBg);
height: var(--height);
font-size: 90%;
}
.title {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: var(--height);
}
.controls {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
height: var(--height);
}
.instanceIcon {
display: inline-block;
width: 20px;
aspect-ratio: 1;
border-radius: 5px;
margin-right: 8px;
}
.instanceTitle {
display: inline-block;
}
.left {
margin-right: auto;
}
.right {
margin-left: auto;
}
.button {
display: inline-block;
height: var(--height);
aspect-ratio: 1;
}
</style>

View file

@ -4,76 +4,80 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root, { [$style.withWallpaper]: withWallpaper }]">
<XSidebar v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'left'"/>
<div :class="[$style.root]">
<XTitlebar v-if="prefer.r.showTitlebar.value" style="flex-shrink: 0;"/>
<div :class="$style.main">
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'"/>
<div :class="$style.nonTitlebarArea">
<XSidebar v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'left'"/>
<XAnnouncements v-if="$i"/>
<XStatusBars/>
<div :class="$style.columnsWrapper">
<!-- passive: https://bugs.webkit.org/show_bug.cgi?id=281300 -->
<div ref="columnsEl" :class="[$style.columns, { [$style.center]: prefer.r['deck.columnAlign'].value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.passive.self="onWheel">
<!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
<section
v-for="ids in layout"
:class="$style.section"
:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
@wheel.passive.self="onWheel"
>
<Suspense>
<component
:is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn"
v-for="id in ids"
:ref="id"
:key="id"
:class="[$style.column, { '_shadow': withWallpaper }]"
:column="columns.find(c => c.id === id)!"
:isStacked="ids.length > 1"
@headerWheel="onWheel"
/>
<template #fallback>
<MkLoading/>
</template>
</Suspense>
</section>
<div v-if="layout.length === 0" class="_panel" :class="$style.onboarding">
<div>{{ i18n.ts._deck.introduction }}</div>
<div>{{ i18n.ts._deck.introduction2 }}</div>
<div :class="[$style.main, { [$style.withWallpaper]: withWallpaper, [$style.withSidebarAndTitlebar]: !isMobile && prefer.r['deck.navbarPosition'].value === 'left' && prefer.r.showTitlebar.value }]" :style="{ backgroundImage: prefer.s['deck.wallpaper'] != null ? `url(${ prefer.s['deck.wallpaper'] })` : null }">
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'"/>
<XAnnouncements v-if="$i"/>
<XStatusBars/>
<div :class="$style.columnsWrapper">
<!-- passive: https://bugs.webkit.org/show_bug.cgi?id=281300 -->
<div ref="columnsEl" :class="[$style.columns, { [$style.center]: prefer.r['deck.columnAlign'].value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.passive.self="onWheel">
<!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
<section
v-for="ids in layout"
:class="$style.section"
:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
@wheel.passive.self="onWheel"
>
<Suspense>
<component
:is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn"
v-for="id in ids"
:ref="id"
:key="id"
:class="[$style.column, { '_shadow': withWallpaper }]"
:column="columns.find(c => c.id === id)!"
:isStacked="ids.length > 1"
@headerWheel="onWheel"
/>
<template #fallback>
<MkLoading/>
</template>
</Suspense>
</section>
<div v-if="layout.length === 0" class="_panel" :class="$style.onboarding">
<div>{{ i18n.ts._deck.introduction }}</div>
<div>{{ i18n.ts._deck.introduction2 }}</div>
</div>
</div>
<div v-if="prefer.r['deck.menuPosition'].value === 'right'" :class="$style.sideMenu">
<div :class="$style.sideMenuTop">
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
</div>
<div :class="$style.sideMenuMiddle">
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.sideMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
</div>
<div :class="$style.sideMenuBottom">
<button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.sideMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
</div>
</div>
</div>
<div v-if="prefer.r['deck.menuPosition'].value === 'right'" :class="$style.sideMenu">
<div :class="$style.sideMenuTop">
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
<div v-if="prefer.r['deck.menuPosition'].value === 'bottom'" :class="$style.bottomMenu">
<div :class="$style.bottomMenuLeft">
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.bottomMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.bottomMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
</div>
<div :class="$style.sideMenuMiddle">
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.sideMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
<div :class="$style.bottomMenuMiddle">
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.bottomMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
</div>
<div :class="$style.sideMenuBottom">
<button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.sideMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
<div :class="$style.bottomMenuRight">
<button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.bottomMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
</div>
</div>
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'bottom'"/>
<XMobileFooterMenu v-if="isMobile" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div>
<div v-if="prefer.r['deck.menuPosition'].value === 'bottom'" :class="$style.bottomMenu">
<div :class="$style.bottomMenuLeft">
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.bottomMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.bottomMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
</div>
<div :class="$style.bottomMenuMiddle">
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.bottomMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
</div>
<div :class="$style.bottomMenuRight">
<button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.bottomMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
</div>
</div>
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'bottom'"/>
<XMobileFooterMenu v-if="isMobile" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div>
<XCommon v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
@ -87,6 +91,7 @@ import XCommon from './_common_/common.vue';
import XSidebar from '@/ui/_common_/navbar.vue';
import XNavbarH from '@/ui/_common_/navbar-h.vue';
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
import XTitlebar from '@/ui/_common_/titlebar.vue';
import * as os from '@/os.js';
import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
@ -216,30 +221,26 @@ async function deleteProfile() {
window.document.documentElement.style.overflowY = 'hidden';
window.document.documentElement.style.scrollBehavior = 'auto';
if (prefer.s['deck.wallpaper'] != null) {
window.document.documentElement.style.backgroundImage = `url(${prefer.s['deck.wallpaper']})`;
}
</script>
<style lang="scss" module>
.root {
$nav-hide-threshold: 650px; // TODO:
--MI-margin: var(--MI-marginHalf);
--columnGap: v-bind("gap + 'px'");
display: flex;
flex-direction: column;
height: 100dvh;
box-sizing: border-box;
flex: 1;
background: var(--MI_THEME-navBg);
}
&.withWallpaper {
.main {
background: transparent;
}
}
.nonTitlebarArea {
display: flex;
flex: 1;
min-height: 0;
}
.main {
@ -247,7 +248,15 @@ if (prefer.s['deck.wallpaper'] != null) {
min-width: 0;
display: flex;
flex-direction: column;
background: var(--MI_THEME-deckBg);
&:not(.withWallpaper) {
background: var(--MI_THEME-deckBg);
}
&.withSidebarAndTitlebar {
border-radius: 12px 0 0 0;
overflow: clip;
}
}
.columnsWrapper {

View file

@ -5,6 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div
class="_forceShrinkSpacer"
:class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready, [$style.withWallpaper]: withWallpaper }]"
@dragover.prevent.stop="onDragover"
@dragleave="onDragleave"
@ -53,7 +54,6 @@ import { DI } from '@/di.js';
provide('shouldHeaderThin', true);
provide('shouldOmitHeaderTitle', true);
provide(DI.forceSpacerMin, true);
const withWallpaper = prefer.s['deck.wallpaper'] != null;

View file

@ -4,22 +4,26 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root">
<XSidebar v-if="!isMobile" :class="$style.sidebar" :showWidgetButton="!isDesktop" @widgetButtonClick="widgetsShowing = true"/>
<div :class="[$style.root, { '_forceShrinkSpacer': deviceKind === 'smartphone' }]">
<XTitlebar v-if="prefer.r.showTitlebar.value" style="flex-shrink: 0;"/>
<div :class="$style.contents" @contextmenu.stop="onContextmenu">
<div>
<XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
<XAnnouncements v-if="$i"/>
<XStatusBars :class="$style.statusbars"/>
<div :class="$style.nonTitlebarArea">
<XSidebar v-if="!isMobile" :class="$style.sidebar" :showWidgetButton="!isDesktop" @widgetButtonClick="widgetsShowing = true"/>
<div :class="[$style.contents, !isMobile && prefer.r.showTitlebar.value ? $style.withSidebarAndTitlebar : null]" @contextmenu.stop="onContextmenu">
<div>
<XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
<XAnnouncements v-if="$i"/>
<XStatusBars :class="$style.statusbars"/>
</div>
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :class="$style.content"/>
<RouterView v-else :class="$style.content"/>
<XMobileFooterMenu v-if="isMobile" ref="navFooter" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div>
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :class="$style.content"/>
<RouterView v-else :class="$style.content"/>
<XMobileFooterMenu v-if="isMobile" ref="navFooter" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div>
<div v-if="isDesktop && !pageMetadata?.needWideArea" :class="$style.widgets">
<XWidgets/>
<div v-if="isDesktop && !pageMetadata?.needWideArea" :class="$style.widgets">
<XWidgets/>
</div>
</div>
<XCommon v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
@ -34,6 +38,7 @@ import XCommon from './_common_/common.vue';
import type { PageMetadata } from '@/page.js';
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue';
import XTitlebar from '@/ui/_common_/titlebar.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/i.js';
@ -128,8 +133,15 @@ $widgets-hide-threshold: 1090px;
height: 100dvh;
overflow: clip;
contain: strict;
box-sizing: border-box;
display: flex;
flex-direction: column;
background: var(--MI_THEME-navBg);
}
.nonTitlebarArea {
display: flex;
flex: 1;
min-height: 0;
}
.sidebar {
@ -142,7 +154,12 @@ $widgets-hide-threshold: 1090px;
flex: 1;
height: 100%;
min-width: 0;
background: var(--MI_THEME-bg);
&.withSidebarAndTitlebar {
background: var(--MI_THEME-navBg);
border-radius: 12px 0 0 0;
overflow: clip;
}
}
.content {