Merge branch 'misskey-dev:develop' into develop

This commit is contained in:
老兄 2023-07-06 14:54:51 +08:00 committed by GitHub
commit 5c399baa5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 107 additions and 55 deletions

View File

@ -21,7 +21,9 @@
### Client
- Fix: サーバーメトリクスが90度傾いている
- Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正
- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるようになりました
- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように
- ドライブファイルのメニューで画像をクロップできるように
- 画像を動画と同様に簡単に隠せるように
### Server
- JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました

View File

@ -121,10 +121,8 @@ export class NoteDeleteService {
}
@bindThis
private async findCascadingNotes(note: Note) {
const cascadingNotes: Note[] = [];
const recursive = async (noteId: string) => {
private async findCascadingNotes(note: Note): Promise<Note[]> {
const recursive = async (noteId: string): Promise<Note[]> => {
const query = this.notesRepository.createQueryBuilder('note')
.where('note.replyId = :noteId', { noteId })
.orWhere(new Brackets(q => {
@ -133,12 +131,14 @@ export class NoteDeleteService {
}))
.leftJoinAndSelect('note.user', 'user');
const replies = await query.getMany();
for (const reply of replies) {
cascadingNotes.push(reply);
await recursive(reply.id);
}
return [
replies,
...await Promise.all(replies.map(reply => recursive(reply.id))),
].flat();
};
await recursive(note.id);
const cascadingNotes: Note[] = await recursive(note.id);
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
}

View File

@ -1,5 +1,4 @@
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import escapeRegexp from 'escape-regexp';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
@ -56,25 +55,18 @@ export class ApDbResolverService implements OnApplicationShutdown {
@bindThis
public parseUri(value: string | IObject): UriParseResult {
const uri = getApId(value);
// the host part of a URL is case insensitive, so use the 'i' flag.
const localRegex = new RegExp('^' + escapeRegexp(this.config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i');
const matchLocal = uri.match(localRegex);
if (matchLocal) {
return {
local: true,
type: matchLocal[1],
id: matchLocal[2],
rest: matchLocal[3],
};
} else {
return {
local: false,
uri,
};
}
const separator = '/';
const uri = new URL(getApId(value));
if (uri.origin !== this.config.url) return { local: false, uri: uri.href };
const [, type, id, ...rest] = uri.pathname.split(separator);
return {
local: true,
type,
id,
rest: rest.length === 0 ? undefined : rest.join(separator),
};
}
/**

View File

@ -47,4 +47,4 @@ html
header#banner(style=`background-image: url(${meta.bannerUrl})`)
div#title= meta.name || host
div#content
div#description= meta.description
div#description!= meta.description

View File

@ -47,6 +47,7 @@ const emit = defineEmits<{
const props = defineProps<{
file: misskey.entities.DriveFile;
aspectRatio: number;
uploadFolder?: string | null;
}>();
const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
@ -58,11 +59,17 @@ let loading = $ref(true);
const ok = async () => {
const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas();
croppedCanvas.toBlob(blob => {
croppedCanvas?.toBlob(blob => {
if (!blob) return;
const formData = new FormData();
formData.append('file', blob);
formData.append('i', $i.token);
if (defaultStore.state.uploadFolder) {
formData.append('name', `cropped_${props.file.name}`);
formData.append('isSensitive', props.file.isSensitive ? 'true' : 'false');
formData.append('comment', props.file.comment ?? 'null');
formData.append('i', $i!.token);
if (props.uploadFolder || props.uploadFolder === null) {
formData.append('folderId', props.uploadFolder ?? 'null');
} else if (defaultStore.state.uploadFolder) {
formData.append('folderId', defaultStore.state.uploadFolder);
}
@ -82,12 +89,12 @@ const ok = async () => {
const f = await promise;
emit('ok', f);
dialogEl.close();
dialogEl!.close();
};
const cancel = () => {
emit('cancel');
dialogEl.close();
dialogEl!.close();
};
const onImageLoad = () => {
@ -100,7 +107,7 @@ const onImageLoad = () => {
};
onMounted(() => {
cropper = new Cropper(imgEl, {
cropper = new Cropper(imgEl!, {
});
const computedStyle = getComputedStyle(document.documentElement);
@ -112,13 +119,13 @@ onMounted(() => {
selection.outlined = true;
window.setTimeout(() => {
cropper.getCropperImage()!.$center('contain');
cropper!.getCropperImage()!.$center('contain');
selection.$center();
}, 100);
// 調
window.setTimeout(() => {
cropper.getCropperImage()!.$center('contain');
cropper!.getCropperImage()!.$center('contain');
selection.$center();
}, 500);
});

View File

@ -44,6 +44,7 @@ import { getDriveFileMenu } from '@/scripts/get-drive-file-menu';
const props = withDefaults(defineProps<{
file: Misskey.entities.DriveFile;
folder: Misskey.entities.DriveFolder | null;
isSelected?: boolean;
selectMode?: boolean;
}>(), {
@ -65,12 +66,12 @@ function onClick(ev: MouseEvent) {
if (props.selectMode) {
emit('chosen', props.file);
} else {
os.popupMenu(getDriveFileMenu(props.file), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
os.popupMenu(getDriveFileMenu(props.file, props.folder), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
}
}
function onContextmenu(ev: MouseEvent) {
os.contextMenu(getDriveFileMenu(props.file), ev);
os.contextMenu(getDriveFileMenu(props.file, props.folder), ev);
}
function onDragstart(ev: DragEvent) {

View File

@ -65,6 +65,7 @@
v-anim="i"
:class="$style.file"
:file="file"
:folder="folder"
:selectMode="select === 'file'"
:isSelected="selectedFiles.some(x => x.id === file.id)"
@chosen="chooseFile"

View File

@ -108,7 +108,7 @@ function waitForDecode() {
.then(() => {
loaded = true;
}, error => {
console.error('Error occured during decoding image', img.value, error);
console.error('Error occurred during decoding image', img.value, error);
throw Error(error);
});
} else {
@ -180,7 +180,7 @@ async function draw() {
render(props.hash, work);
drawImage(work);
} catch (error) {
console.error('Error occured during drawing blurhash', error);
console.error('Error occurred during drawing blurhash', error);
}
}
}

View File

@ -33,6 +33,7 @@
<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);">NSFW</div>
</div>
<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;"></i></button>
<i class="ti ti-eye-off" :class="$style.hide" @click.stop="hide = true"></i>
</template>
</div>
</template>
@ -113,6 +114,21 @@ function showMenu(ev: MouseEvent) {
align-items: center;
}
.hide {
display: block;
position: absolute;
border-radius: 6px;
background-color: var(--fg);
color: var(--accentLighten);
font-size: 12px;
opacity: .5;
padding: 5px 8px;
text-align: center;
cursor: pointer;
top: 12px;
right: 12px;
}
.hiddenTextWrapper {
display: table-cell;
text-align: center;
@ -137,8 +153,8 @@ function showMenu(ev: MouseEvent) {
backdrop-filter: var(--blur, blur(15px));
color: #fff;
font-size: 0.8em;
width: 32px;
height: 32px;
width: 28px;
height: 28px;
text-align: center;
bottom: 10px;
right: 10px;

View File

@ -188,6 +188,7 @@ watch(queue, (a, b) => {
}, { deep: true });
async function init(): Promise<void> {
items.value = new Map();
queue.value = new Map();
fetching.value = true;
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
@ -219,8 +220,6 @@ async function init(): Promise<void> {
}
const reload = (): Promise<void> => {
items.value = new Map();
queue.value = new Map();
return init();
};

View File

@ -66,7 +66,7 @@
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
</div>
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName" @replaceFile="replaceFile"/>
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text"/>
<div v-if="showingOptions" style="padding: 8px 16px;">
@ -410,7 +410,11 @@ function updateFileName(file, name) {
files[files.findIndex(x => x.id === file.id)].name = name;
}
function upload(file: File, name?: string) {
function replaceFile(file: misskey.entities.DriveFile, newFile: misskey.entities.DriveFile): void {
files[files.findIndex(x => x.id === file.id)] = newFile;
}
function upload(file: File, name?: string): void {
uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
files.push(res);
});

View File

@ -16,6 +16,7 @@
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import * as misskey from 'misskey-js';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
@ -30,8 +31,9 @@ const props = defineProps<{
const emit = defineEmits<{
(ev: 'update:modelValue', value: any[]): void;
(ev: 'detach', id: string): void;
(ev: 'changeSensitive'): void;
(ev: 'changeName'): void;
(ev: 'changeSensitive', file: misskey.entities.DriveFile, isSensitive: boolean): void;
(ev: 'changeName', file: misskey.entities.DriveFile, newName: string): void;
(ev: 'replaceFile', file: misskey.entities.DriveFile, newFile: misskey.entities.DriveFile): void;
}>();
let menuShowing = false;
@ -85,8 +87,15 @@ async function describe(file) {
}, 'closed');
}
function showFileMenu(file, ev: MouseEvent) {
async function crop(file: misskey.entities.DriveFile): Promise<void> {
const newFile = await os.cropImage(file, { aspectRatio: NaN });
emit('replaceFile', file, newFile);
}
function showFileMenu(file: misskey.entities.DriveFile, ev: MouseEvent): void {
if (menuShowing) return;
const isImage = file.type.startsWith('image/');
os.popupMenu([{
text: i18n.ts.renameFile,
icon: 'ti ti-forms',
@ -99,7 +108,11 @@ function showFileMenu(file, ev: MouseEvent) {
text: i18n.ts.describeFile,
icon: 'ti ti-text-caption',
action: () => { describe(file); },
}, {
}, ...isImage ? [{
text: i18n.ts.cropImage,
icon: 'ti ti-crop',
action: () : void => { crop(file); },
}] : [], {
text: i18n.ts.attachCancel,
icon: 'ti ti-circle-x',
action: () => { detachMedia(file.id); },

View File

@ -460,11 +460,13 @@ export async function pickEmoji(src: HTMLElement | null, opts) {
export async function cropImage(image: Misskey.entities.DriveFile, options: {
aspectRatio: number;
uploadFolder?: string | null;
}): Promise<Misskey.entities.DriveFile> {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/MkCropperDialog.vue')), {
file: image,
aspectRatio: options.aspectRatio,
uploadFolder: options.uploadFolder,
}, {
ok: x => {
resolve(x);

View File

@ -155,6 +155,12 @@ const patronsWithIcon = [{
}, {
name: 'spinlock',
icon: 'https://misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg',
}, {
name: 'じゅくま',
icon: 'https://misskey-hub.net/patrons/3e56bdac69dd42f7a06e0f12cf2fc895.jpg',
}, {
name: '清遊あみ',
icon: 'https://misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg',
}];
const patrons = [

View File

@ -3,6 +3,7 @@ import { defineAsyncComponent } from 'vue';
import { i18n } from '@/i18n';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import * as os from '@/os';
import { MenuItem } from '@/types/menu';
function rename(file: Misskey.entities.DriveFile) {
os.inputText({
@ -66,7 +67,8 @@ async function deleteFile(file: Misskey.entities.DriveFile) {
});
}
export function getDriveFileMenu(file: Misskey.entities.DriveFile) {
export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Misskey.entities.DriveFolder | null): MenuItem[] {
const isImage = file.type.startsWith('image/');
return [{
text: i18n.ts.rename,
icon: 'ti ti-forms',
@ -79,7 +81,14 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile) {
text: i18n.ts.describeFile,
icon: 'ti ti-text-caption',
action: () => describe(file),
}, null, {
}, ...isImage ? [{
text: i18n.ts.cropImage,
icon: 'ti ti-crop',
action: () => os.cropImage(file, {
aspectRatio: NaN,
uploadFolder: folder ? folder.id : folder
}),
}] : [], null, {
text: i18n.ts.createNoteFromTheFile,
icon: 'ti ti-pencil',
action: () => os.post({