Compare commits

...

8 Commits

Author SHA1 Message Date
zyoshoka d91a4e3dec
fix(ci): change Chromatic build to be triggered when frontend `package.json` is edited instead of lockfile (#15793)
* fix(ci): correct invalid condition for skipping Chromatic build

* fix: change to be triggered when frontend `package.json` is edited instead of lockfile

* chore: disable automatic rebase of frontend Renovate PRs
2025-04-27 10:49:23 +09:00
かっこかり 6a69e4180b
fix(frontend): PageWithHeaderからPageHeaderに全Propsが伝わっていなかった問題を修正 (#15858) 2025-04-27 10:46:55 +09:00
syuilo 3b3bb36c49 enhance(frontend): 通知ページをスワイプで切り替えられるように 2025-04-27 10:14:41 +09:00
syuilo 69cfd34545 Update CHANGELOG.md 2025-04-27 10:01:44 +09:00
syuilo 9481b5a6e8 feat: アップロード可能な最大ファイルサイズをロールごとに設定可能に 2025-04-27 09:35:44 +09:00
github-actions[bot] effc84b9cc Bump version to 2025.4.1-beta.0 2025-04-27 00:22:13 +00:00
なっかあ de073d6d69
Fix #15876 絵文字がアニメーションしない問題を修正 (#15881) 2025-04-27 09:16:41 +09:00
syuilo 5042a0aa8f fix(frontend): タイムラインでノートが重複して表示されることがあるのを修正
Fix #15870
Close #15874
2025-04-27 09:16:17 +09:00
22 changed files with 105 additions and 43 deletions

View File

@ -15,11 +15,7 @@ on:
jobs:
build:
# Chromatic is not likely to be available for fork repositories, so we disable for fork repositories.
# Neither Dependabot nor Renovate will change the actual behavior for components.
if: >-
github.repository == 'misskey-dev/misskey' &&
startsWith(github.head_ref, 'refs/heads/dependabot/') != true &&
startsWith(github.head_ref, 'refs/heads/renovate/') != true
if: github.repository == 'misskey-dev/misskey'
runs-on: ubuntu-latest
env:

View File

@ -2,6 +2,8 @@
### General
- Feat: bull-boardに代わるジョブキューの管理ツールが実装されました
- Feat: アップロード可能な最大ファイルサイズをロールごとに設定可能に
- デフォルトで10MBになっています
- Enhance: チャットの新規メッセージをプッシュ通知するように
### Client
@ -17,6 +19,7 @@
- Fix: タイムラインのスクロール位置を記憶するように修正
- Fix: ノートの直後のノートを表示する機能で表示が逆順になっていた問題を修正 #15841
- Fix: アカウントの移行時にアンテナのフィルターのユーザが更新されない問題を修正 #15843
- Fix: タイムラインでノートが重複して表示されることがあるのを修正
### Server
- Enhance: ジョブキューの成功/失敗したジョブも一定数・一定期間保存するようにし、後から問題を調査することを容易に

4
locales/index.d.ts vendored
View File

@ -7464,6 +7464,10 @@ export interface Locale extends ILocale {
*
*/
"driveCapacity": string;
/**
*
*/
"maxFileSize": string;
/**
* NSFWを常に付与
*/

View File

@ -1934,6 +1934,7 @@ _role:
canManageCustomEmojis: "カスタム絵文字の管理"
canManageAvatarDecorations: "アバターデコレーションの管理"
driveCapacity: "ドライブ容量"
maxFileSize: "アップロード可能な最大ファイルサイズ"
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
canUpdateBioMedia: "アイコンとバナーの更新を許可"
pinMax: "ノートのピン留めの最大数"

View File

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.4.1-alpha.2",
"version": "2025.4.1-beta.0",
"codename": "nasubi",
"repository": {
"type": "git",

View File

@ -522,9 +522,16 @@ export class DriveService {
const policies = await this.roleService.getUserPolicies(user.id);
const driveCapacity = 1024 * 1024 * policies.driveCapacityMb;
const maxFileSize = 1024 * 1024 * policies.maxFileSizeMb;
this.registerLogger.debug('drive capacity override applied');
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
if (maxFileSize < info.size) {
if (isLocalUser) {
throw new IdentifiableError('f9e4e5f3-4df4-40b5-b400-f236945f7073', 'Max file size exceeded.');
}
}
// If usage limit exceeded
if (driveCapacity < usage + info.size) {
if (isLocalUser) {

View File

@ -34,6 +34,7 @@ export const webpDefault: sharp.WebpOptions = {
smartSubsample: true,
mixed: true,
effort: 2,
loop: 0,
};
export const avifDefault: sharp.AvifOptions = {

View File

@ -46,6 +46,7 @@ export type RolePolicies = {
canUseTranslator: boolean;
canHideAds: boolean;
driveCapacityMb: number;
maxFileSizeMb: number;
alwaysMarkNsfw: boolean;
canUpdateBioMedia: boolean;
pinLimit: number;
@ -81,6 +82,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
canUseTranslator: true,
canHideAds: false,
driveCapacityMb: 100,
maxFileSizeMb: 10,
alwaysMarkNsfw: false,
canUpdateBioMedia: true,
pinLimit: 5,
@ -391,6 +393,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
maxFileSizeMb: calc('maxFileSizeMb', vs => Math.max(...vs)),
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
canUpdateBioMedia: calc('canUpdateBioMedia', vs => vs.some(v => v === true)),
pinLimit: calc('pinLimit', vs => Math.max(...vs)),

View File

@ -224,6 +224,10 @@ export const packedRolePoliciesSchema = {
type: 'integer',
optional: false, nullable: false,
},
maxFileSizeMb: {
type: 'integer',
optional: false, nullable: false,
},
alwaysMarkNsfw: {
type: 'boolean',
optional: false, nullable: false,

View File

@ -10,9 +10,9 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { DriveService } from '@/core/DriveService.js';
import { ApiError } from '../../../error.js';
import { MiMeta } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['drive'],
@ -56,6 +56,12 @@ export const meta = {
code: 'NO_FREE_SPACE',
id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064',
},
maxFileSizeExceeded: {
message: 'Cannot upload the file because it exceeds the maximum file size.',
code: 'MAX_FILE_SIZE_EXCEEDED',
id: 'b9d8c348-33f0-4673-b9a9-5d4da058977a',
},
},
} as const;
@ -115,6 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (err instanceof IdentifiableError) {
if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate);
if (err.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace);
if (err.id === 'f9e4e5f3-4df4-40b5-b400-f236945f7073') throw new ApiError(meta.errors.maxFileSizeExceeded);
}
throw new ApiError();
} finally {

View File

@ -91,6 +91,7 @@ export const ROLE_POLICIES = [
'canUseTranslator',
'canHideAds',
'driveCapacityMb',
'maxFileSizeMb',
'alwaysMarkNsfw',
'canUpdateBioMedia',
'pinLimit',

View File

@ -55,7 +55,7 @@ await fs.readFile(
'../../locales/ja-JP.yml',
'assets/**',
'public/**',
'../../pnpm-lock.yaml',
'package.json',
]).length
) {
return;

View File

@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
</div>
</div>
<MkNote :class="$style.note" :note="note" :withHardMute="true" :data-scroll-anchor="note.id"/>
<MkNote v-else :class="$style.note" :note="note" :withHardMute="true" :data-scroll-anchor="note.id"/>
</template>
</component>
</template>

View File

@ -40,19 +40,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'vue';
import { scrollToTop } from '@@/js/scroll.js';
import XTabs from './MkPageHeader.tabs.vue';
import type { Tab } from './MkPageHeader.tabs.vue';
<script lang="ts">
import type { PageHeaderItem } from '@/types/page-header.js';
import type { PageMetadata } from '@/page.js';
import { globalEvents } from '@/events.js';
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
import { $i } from '@/i.js';
import { DI } from '@/di.js';
import type { Tab } from './MkPageHeader.tabs.vue';
const props = withDefaults(defineProps<{
export type PageHeaderProps = {
overridePageMetadata?: PageMetadata;
tabs?: Tab[];
tab?: string;
@ -60,7 +53,19 @@ const props = withDefaults(defineProps<{
thin?: boolean;
hideTitle?: boolean;
displayMyAvatar?: boolean;
}>(), {
};
</script>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'vue';
import { scrollToTop } from '@@/js/scroll.js';
import XTabs from './MkPageHeader.tabs.vue';
import { globalEvents } from '@/events.js';
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
import { $i } from '@/i.js';
import { DI } from '@/di.js';
const props = withDefaults(defineProps<PageHeaderProps>(), {
tabs: () => ([] as Tab[]),
});

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div ref="rootEl" :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template>
<template #header><MkPageHeader v-model:tab="tab" v-bind="pageHeaderProps"/></template>
<div :class="$style.body">
<slot></slot>
</div>
@ -16,21 +16,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import { computed, useTemplateRef } from 'vue';
import { scrollInContainer } from '@@/js/scroll.js';
import type { PageHeaderItem } from '@/types/page-header.js';
import type { Tab } from './MkPageHeader.tabs.vue';
import type { PageHeaderProps } from './MkPageHeader.vue';
import { useScrollPositionKeeper } from '@/use/use-scroll-position-keeper.js';
const props = withDefaults(defineProps<{
tabs?: Tab[];
actions?: PageHeaderItem[] | null;
thin?: boolean;
hideTitle?: boolean;
displayMyAvatar?: boolean;
const props = defineProps<PageHeaderProps & {
reversed?: boolean;
}>(), {
tabs: () => ([] as Tab[]),
}>();
const pageHeaderProps = computed(() => {
const { reversed, ...rest } = props;
return rest;
});
const tab = defineModel<string>('tab');

View File

@ -386,6 +386,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.maxFileSize, 'maxFileSizeMb'])">
<template #label>{{ i18n.ts._role._options.maxFileSize }}</template>
<template #suffix>
<span v-if="role.policies.maxFileSizeMb.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ role.policies.maxFileSizeMb.value + 'MB' }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.maxFileSizeMb)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="role.policies.maxFileSizeMb.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
</MkSwitch>
<MkInput v-model="role.policies.maxFileSizeMb.value" :disabled="role.policies.maxFileSizeMb.useDefault" type="number" :readonly="readonly">
<template #suffix>MB</template>
</MkInput>
<MkRange v-model="role.policies.maxFileSizeMb.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
<template #label>{{ i18n.ts._role.priority }}</template>
</MkRange>
</div>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.alwaysMarkNsfw, 'alwaysMarkNsfw'])">
<template #label>{{ i18n.ts._role._options.alwaysMarkNsfw }}</template>
<template #suffix>

View File

@ -138,6 +138,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkInput>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.maxFileSize, 'maxFileSizeMb'])">
<template #label>{{ i18n.ts._role._options.maxFileSize }}</template>
<template #suffix>{{ policies.maxFileSizeMb }}MB</template>
<MkInput v-model="policies.maxFileSizeMb" type="number">
<template #suffix>MB</template>
</MkInput>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.alwaysMarkNsfw, 'alwaysMarkNsfw'])">
<template #label>{{ i18n.ts._role._options.alwaysMarkNsfw }}</template>
<template #suffix>{{ policies.alwaysMarkNsfw ? i18n.ts.yes : i18n.ts.no }}</template>

View File

@ -6,15 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<div v-if="tab === 'all'">
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
</div>
<div v-else-if="tab === 'mentions'">
<MkNotes :pagination="mentionsPagination"/>
</div>
<div v-else-if="tab === 'directNotes'">
<MkNotes :pagination="directNotesPagination"/>
</div>
<MkSwiper v-model:tab="tab" :tabs="headerTabs">
<div v-if="tab === 'all'">
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
</div>
<div v-else-if="tab === 'mentions'">
<MkNotes :pagination="mentionsPagination"/>
</div>
<div v-else-if="tab === 'directNotes'">
<MkNotes :pagination="directNotesPagination"/>
</div>
</MkSwiper>
</MkSpacer>
</PageWithHeader>
</template>

View File

@ -40,7 +40,7 @@ export function uploadFile(
const _folder = typeof folder === 'string' ? folder : folder?.id;
if (file.size > instance.maxFileSize) {
if ((file.size > instance.maxFileSize) || (file.size > ($i.policies.maxFileSizeMb * 1024 * 1024))) {
alert({
type: 'error',
title: i18n.ts.failedToUpload,

View File

@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2025.4.1-alpha.2",
"version": "2025.4.1-beta.0",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",

View File

@ -5216,6 +5216,7 @@ export type components = {
canUseTranslator: boolean;
canHideAds: boolean;
driveCapacityMb: number;
maxFileSizeMb: number;
alwaysMarkNsfw: boolean;
canUpdateBioMedia: boolean;
pinLimit: number;

View File

@ -38,6 +38,8 @@
'packages/misskey-reversi/**/package.json',
'packages/sw/**/package.json',
],
// prevent wastage of Chromatic snapshots
rebaseWhen: 'never',
},
{
groupName: '[misskey-js] Update dependencies',