Compare commits

...

8 Commits

Author SHA1 Message Date
dependabot[bot] 5533b03f67
Merge 2d65b0a7a5 into 887c709647 2024-09-15 11:54:31 +00:00
dependabot[bot] 887c709647
chore(deps): bump body-parser from 1.20.2 to 1.20.3 in /packages/backend (#14550)
Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.2 to 1.20.3.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-15 20:54:26 +09:00
かっこかり 0e4b6d1dad
enhance(frontend): adminのファイルリストでセンシティブファイルに枠線を追加 (#14510)
* enhance(frontend): adminのファイルリストでセンシティブファイルに枠線を追加

* Update Changelog
2024-09-15 17:50:25 +09:00
Juan Aguilar Santillana 07f26bc8dd
refactor(backend): use Reflet for autobind deco (#14482)
Using Reflect.defineProperty instead of Object.defineProperty
gives a more consistent behavior with the rest of the modern
JavaScript features.
2024-09-15 17:43:24 +09:00
syuilo 366b79e459
Update CHANGELOG.md 2024-09-15 15:14:13 +09:00
Kisaragi 6b2072f4b1
fix(backend/antenna): キーワードが与えられなかった場合のエラーをApiErrorとして投げる (#14491)
* fix(backend/antenna): report validation failure as ApiError on update

* test(backend/antenna): reflect change in previous commit

* fix(backend/antenna): report validation failure as ApiError on create

* test(backend/antenna): reflect change in previous commit

* test(backend/antenna): semi

* test(backend/antenna): bring being spread parameters first in object literal

* chore: add CHANGELOG entry

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2024-09-15 15:13:46 +09:00
かっこかり 1544ba9153
refactor(frontend): 非推奨の表現を改める (#14517) 2024-09-15 12:31:17 +09:00
dependabot[bot] 2d65b0a7a5
chore(deps-dev): bump vite in /scripts/changelog-checker
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.12 to 5.0.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.13/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.13/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 17:55:34 +00:00
13 changed files with 107 additions and 59 deletions

View File

@ -8,15 +8,17 @@
- 埋め込みコードやウェブサイトへの実装方法の詳細はMisskey Hubに掲載予定です
- Enhance: サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように
- Enhance: アイコンデコレーション管理画面にプレビューを追加
- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
### Server
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
- この変更により、公式フロントエンドでは入力の不備が内部エラーとして報告される代わりに一般的なエラーダイアログで報告されます
- Fix: ファイルがサイズの制限を超えてアップロードされた際にエラーを返さなかった問題を修正
- Fix: 外部ページを解析する際に、ページに紐づけられた関連リソースも読み込まれてしまう問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/26e0412fbb91447c37e8fb06ffb0487346063bb8)
## 2024.8.0
### General

View File

@ -100,7 +100,7 @@
"async-mutex": "0.5.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"bullmq": "5.10.4",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.2",

View File

@ -10,8 +10,9 @@
* The getter will return a .bind version of the function
* and memoize the result against a symbol on the instance
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function bindThis(target: any, key: string, descriptor: any) {
let fn = descriptor.value;
const fn = descriptor.value;
if (typeof fn !== 'function') {
throw new TypeError(`@bindThis decorator can only be applied to methods not: ${typeof fn}`);
@ -21,26 +22,18 @@ export function bindThis(target: any, key: string, descriptor: any) {
configurable: true,
get() {
// eslint-disable-next-line no-prototype-builtins
if (this === target.prototype || this.hasOwnProperty(key) ||
typeof fn !== 'function') {
if (this === target.prototype || this.hasOwnProperty(key)) {
return fn;
}
const boundFn = fn.bind(this);
Object.defineProperty(this, key, {
Reflect.defineProperty(this, key, {
value: boundFn,
configurable: true,
get() {
return boundFn;
},
set(value) {
fn = value;
delete this[key];
},
writable: true,
});
return boundFn;
},
set(value: any) {
fn = value;
},
};
}

View File

@ -34,6 +34,12 @@ export const meta = {
code: 'TOO_MANY_ANTENNAS',
id: 'faf47050-e8b5-438c-913c-db2b1576fde4',
},
emptyKeyword: {
message: 'Either keywords or excludeKeywords is required.',
code: 'EMPTY_KEYWORD',
id: '53ee222e-1ddd-4f9a-92e5-9fb82ddb463a',
},
},
res: {
@ -87,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) {
throw new Error('either keywords or excludeKeywords is required.');
throw new ApiError(meta.errors.emptyKeyword);
}
const currentAntennasCount = await this.antennasRepository.countBy({

View File

@ -32,6 +32,12 @@ export const meta = {
code: 'NO_SUCH_USER_LIST',
id: '1c6b35c9-943e-48c2-81e4-2844989407f7',
},
emptyKeyword: {
message: 'Either keywords or excludeKeywords is required.',
code: 'EMPTY_KEYWORD',
id: '721aaff6-4e1b-4d88-8de6-877fae9f68c4',
},
},
res: {
@ -85,7 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
if (ps.keywords && ps.excludeKeywords) {
if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) {
throw new Error('either keywords or excludeKeywords is required.');
throw new ApiError(meta.errors.emptyKeyword);
}
}
// Fetch the antenna

View File

@ -228,6 +228,17 @@ describe('アンテナ', () => {
assert.deepStrictEqual(response, expected);
});
test('を作成する時キーワードが指定されていないとエラーになる', async () => {
await failedApiCall({
endpoint: 'antennas/create',
parameters: { ...defaultParam, keywords: [[]], excludeKeywords: [[]] },
user: alice
}, {
status: 400,
code: 'EMPTY_KEYWORD',
id: '53ee222e-1ddd-4f9a-92e5-9fb82ddb463a'
})
});
//#endregion
//#region 更新(antennas/update)
@ -255,6 +266,18 @@ describe('アンテナ', () => {
id: '1c6b35c9-943e-48c2-81e4-2844989407f7',
});
});
test('を変更する時キーワードが指定されていないとエラーになる', async () => {
const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice });
await failedApiCall({
endpoint: 'antennas/update',
parameters: { ...defaultParam, antennaId: antenna.id, keywords: [[]], excludeKeywords: [[]] },
user: alice
}, {
status: 400,
code: 'EMPTY_KEYWORD',
id: '721aaff6-4e1b-4d88-8de6-877fae9f68c4'
})
});
//#endregion
//#region 表示(antennas/show)

View File

@ -4,7 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div ref="thumbnail" :class="$style.root">
<div
ref="thumbnail"
:class="[
$style.root,
{ [$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive },
]"
>
<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/>
<i v-else-if="is === 'image'" class="ti ti-photo" :class="$style.icon"></i>
<i v-else-if="is === 'video'" class="ti ti-video" :class="$style.icon"></i>
@ -27,6 +33,7 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
const props = defineProps<{
file: Misskey.entities.DriveFile;
fit: 'cover' | 'contain';
highlightWhenSensitive?: boolean;
}>();
const is = computed(() => {
@ -67,6 +74,18 @@ const isThumbnailAvailable = computed(() => {
overflow: clip;
}
.sensitiveHighlight::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
border-radius: inherit;
box-shadow: inset 0 0 0 4px var(--warn);
}
.iconSub {
position: absolute;
width: 30%;

View File

@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
class="file _button"
>
<div v-if="file.isSensitive" class="sensitive-label">{{ i18n.ts.sensitive }}</div>
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain" :highlightWhenSensitive="true"/>
<div v-if="viewMode === 'list'" class="body">
<div>
<small style="opacity: 0.7;">{{ file.name }}</small>

View File

@ -63,7 +63,7 @@ async function detachAndDeleteMedia(file: Misskey.entities.DriveFile) {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.t('driveFileDeleteConfirm', { name: file.name }),
text: i18n.tsx.driveFileDeleteConfirm({ name: file.name }),
});
if (canceled) return;

View File

@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps_m">
<div :class="$style.buttons">
<MkButton inline primary @click="saveNew">{{ ts._preferencesBackups.saveNew }}</MkButton>
<MkButton inline @click="loadFile">{{ ts._preferencesBackups.loadFile }}</MkButton>
<MkButton inline primary @click="saveNew">{{ i18n.ts._preferencesBackups.saveNew }}</MkButton>
<MkButton inline @click="loadFile">{{ i18n.ts._preferencesBackups.loadFile }}</MkButton>
</div>
<FormSection>
<template #label>{{ ts._preferencesBackups.list }}</template>
<template #label>{{ i18n.ts._preferencesBackups.list }}</template>
<template v-if="profiles && Object.keys(profiles).length > 0">
<div class="_gaps_s">
<div
@ -23,13 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only
@contextmenu.prevent.stop="$event => menu($event, id)"
>
<div :class="$style.profileName">{{ profile.name }}</div>
<div :class="$style.profileTime">{{ t('_preferencesBackups.createdAt', { date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div>
<div v-if="profile.updatedAt" :class="$style.profileTime">{{ t('_preferencesBackups.updatedAt', { date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div>
<div :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.createdAt({ date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div>
<div v-if="profile.updatedAt" :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.updatedAt({ date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div>
</div>
</div>
</template>
<div v-else-if="profiles">
<MkInfo>{{ ts._preferencesBackups.noBackups }}</MkInfo>
<MkInfo>{{ i18n.ts._preferencesBackups.noBackups }}</MkInfo>
</div>
<MkLoading v-else/>
</FormSection>
@ -52,7 +52,6 @@ import { i18n } from '@/i18n.js';
import { version, host } from '@@/js/config.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js';
const { t, ts } = i18n;
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'collapseRenotes',
@ -201,15 +200,15 @@ async function saveNew(): Promise<void> {
if (!profiles.value) return;
const { canceled, result: name } = await os.inputText({
title: ts._preferencesBackups.inputName,
title: i18n.ts._preferencesBackups.inputName,
default: '',
});
if (canceled) return;
if (Object.values(profiles.value).some(x => x.name === name)) {
return os.alert({
title: ts._preferencesBackups.cannotSave,
text: t('_preferencesBackups.nameAlreadyExists', { name }),
title: i18n.ts._preferencesBackups.cannotSave,
text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }),
});
}
@ -238,8 +237,8 @@ function loadFile(): void {
if (file.type !== 'application/json') {
return os.alert({
type: 'error',
title: ts._preferencesBackups.cannotLoad,
text: ts._preferencesBackups.invalidFile,
title: i18n.ts._preferencesBackups.cannotLoad,
text: i18n.ts._preferencesBackups.invalidFile,
});
}
@ -250,7 +249,7 @@ function loadFile(): void {
} catch (err) {
return os.alert({
type: 'error',
title: ts._preferencesBackups.cannotLoad,
title: i18n.ts._preferencesBackups.cannotLoad,
text: (err as any)?.message ?? '',
});
}
@ -276,8 +275,8 @@ async function applyProfile(id: string): Promise<void> {
const { canceled: cancel1 } = await os.confirm({
type: 'warning',
title: ts._preferencesBackups.apply,
text: t('_preferencesBackups.applyConfirm', { name: profile.name }),
title: i18n.ts._preferencesBackups.apply,
text: i18n.tsx._preferencesBackups.applyConfirm({ name: profile.name }),
});
if (cancel1) return;
@ -322,7 +321,7 @@ async function applyProfile(id: string): Promise<void> {
const { canceled: cancel2 } = await os.confirm({
type: 'info',
text: ts.reloadToApplySetting,
text: i18n.ts.reloadToApplySetting,
});
if (cancel2) return;
@ -334,8 +333,8 @@ async function deleteProfile(id: string): Promise<void> {
const { canceled } = await os.confirm({
type: 'info',
title: ts.delete,
text: t('deleteAreYouSure', { x: profiles.value[id].name }),
title: i18n.ts.delete,
text: i18n.tsx.deleteAreYouSure({ x: profiles.value[id].name }),
});
if (canceled) return;
@ -350,8 +349,8 @@ async function save(id: string): Promise<void> {
const { canceled } = await os.confirm({
type: 'info',
title: ts._preferencesBackups.save,
text: t('_preferencesBackups.saveConfirm', { name }),
title: i18n.ts._preferencesBackups.save,
text: i18n.tsx._preferencesBackups.saveConfirm({ name }),
});
if (canceled) return;
@ -370,15 +369,15 @@ async function rename(id: string): Promise<void> {
if (!profiles.value) return;
const { canceled: cancel1, result: name } = await os.inputText({
title: ts._preferencesBackups.inputName,
title: i18n.ts._preferencesBackups.inputName,
default: '',
});
if (cancel1 || profiles.value[id].name === name) return;
if (Object.values(profiles.value).some(x => x.name === name)) {
return os.alert({
title: ts._preferencesBackups.cannotSave,
text: t('_preferencesBackups.nameAlreadyExists', { name }),
title: i18n.ts._preferencesBackups.cannotSave,
text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }),
});
}
@ -386,8 +385,8 @@ async function rename(id: string): Promise<void> {
const { canceled: cancel2 } = await os.confirm({
type: 'info',
title: ts.rename,
text: t('_preferencesBackups.renameConfirm', { old: registry.name, new: name }),
title: i18n.ts.rename,
text: i18n.tsx._preferencesBackups.renameConfirm({ old: registry.name, new: name }),
});
if (cancel2) return;
@ -399,25 +398,25 @@ function menu(ev: MouseEvent, profileId: string) {
if (!profiles.value) return;
return os.popupMenu([{
text: ts._preferencesBackups.apply,
text: i18n.ts._preferencesBackups.apply,
icon: 'ti ti-check',
action: () => applyProfile(profileId),
}, {
type: 'a',
text: ts.download,
text: i18n.ts.download,
icon: 'ti ti-download',
href: URL.createObjectURL(new Blob([JSON.stringify(profiles.value[profileId], null, 2)], { type: 'application/json' })),
download: `${profiles.value[profileId].name}.json`,
}, { type: 'divider' }, {
text: ts.rename,
text: i18n.ts.rename,
icon: 'ti ti-forms',
action: () => rename(profileId),
}, {
text: ts._preferencesBackups.save,
text: i18n.ts._preferencesBackups.save,
icon: 'ti ti-device-floppy',
action: () => save(profileId),
}, { type: 'divider' }, {
text: ts.delete,
text: i18n.ts.delete,
icon: 'ti ti-trash',
action: () => deleteProfile(profileId),
danger: true,
@ -439,7 +438,7 @@ onUnmounted(() => {
});
definePageMetadata(() => ({
title: ts.preferencesBackups,
title: i18n.ts.preferencesBackups,
icon: 'ti ti-device-floppy',
}));
</script>

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { getHighlighterCore, loadWasm } from 'shiki/core';
import { createHighlighterCore, loadWasm } from 'shiki/core';
import darkPlus from 'shiki/themes/dark-plus.mjs';
import { bundledThemesInfo } from 'shiki/themes';
import { bundledLanguagesInfo } from 'shiki/langs';
@ -69,7 +69,7 @@ async function initHighlighter() {
]);
const jsLangInfo = bundledLanguagesInfo.find(t => t.id === 'javascript');
const highlighter = await getHighlighterCore({
const highlighter = await createHighlighterCore({
themes,
langs: [
...(jsLangInfo ? [async () => await jsLangInfo.import()] : []),

View File

@ -16,7 +16,7 @@
"remark-parse": "11.0.0",
"typescript": "5.3.3",
"unified": "11.0.4",
"vite": "5.0.12",
"vite": "5.0.13",
"vite-node": "1.1.3",
"vitest": "1.1.3"
}
@ -2558,9 +2558,9 @@
}
},
"node_modules/vite": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",
"integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==",
"version": "5.0.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.13.tgz",
"integrity": "sha512-/9ovhv2M2dGTuA+dY93B9trfyWMDRQw2jdVBhHNP6wr0oF34wG2i/N55801iZIpgUpnHDm4F/FabGQLyc+eOgg==",
"dev": true,
"dependencies": {
"esbuild": "^0.19.3",

View File

@ -17,7 +17,7 @@
"remark-parse": "11.0.0",
"typescript": "5.3.3",
"unified": "11.0.4",
"vite": "5.0.12",
"vite": "5.0.13",
"vite-node": "1.1.3",
"vitest": "1.1.3"
}