Merge branch 'develop' into fix-7311

This commit is contained in:
かっこかり 2024-06-01 16:47:30 +09:00 committed by GitHub
commit 21e5fe6870
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
285 changed files with 21111 additions and 13767 deletions

View file

@ -63,3 +63,26 @@ export async function lookupUserByEmail() {
}
}
}
export async function lookupFile() {
const { canceled, result: q } = await os.inputText({
title: i18n.ts.fileIdOrUrl,
minLength: 1,
});
if (canceled) return;
const show = (file) => {
os.pageWindow(`/admin/file/${file.id}`);
};
misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => {
show(file);
}).catch(err => {
if (err.code === 'NO_SUCH_FILE') {
os.alert({
type: 'error',
text: i18n.ts.notFound,
});
}
});
}

View file

@ -6,6 +6,7 @@
import { utils, values } from '@syuilo/aiscript';
import { v4 as uuid } from 'uuid';
import { ref, Ref } from 'vue';
import * as Misskey from 'misskey-js';
export type AsUiComponentBase = {
id: string;
@ -115,23 +116,24 @@ export type AsUiFolder = AsUiComponentBase & {
opened?: boolean;
};
type PostFormPropsForAsUi = {
text: string;
cw?: string;
visibility?: (typeof Misskey.noteVisibilities)[number];
localOnly?: boolean;
};
export type AsUiPostFormButton = AsUiComponentBase & {
type: 'postFormButton';
text?: string;
primary?: boolean;
rounded?: boolean;
form?: {
text: string;
cw?: string;
};
form?: PostFormPropsForAsUi;
};
export type AsUiPostForm = AsUiComponentBase & {
type: 'postForm';
form?: {
text: string;
cw?: string;
};
form?: PostFormPropsForAsUi;
};
export type AsUiComponent = AsUiRoot | AsUiContainer | AsUiText | AsUiMfm | AsUiButton | AsUiButtons | AsUiSwitch | AsUiTextarea | AsUiTextInput | AsUiNumberInput | AsUiSelect | AsUiFolder | AsUiPostFormButton | AsUiPostForm;
@ -447,6 +449,24 @@ function getFolderOptions(def: values.Value | undefined): Omit<AsUiFolder, 'id'
};
}
function getPostFormProps(form: values.VObj): PostFormPropsForAsUi {
const text = form.value.get('text');
utils.assertString(text);
const cw = form.value.get('cw');
if (cw) utils.assertString(cw);
const visibility = form.value.get('visibility');
if (visibility) utils.assertString(visibility);
const localOnly = form.value.get('localOnly');
if (localOnly) utils.assertBoolean(localOnly);
return {
text: text.value,
cw: cw?.value,
visibility: (visibility?.value && (Misskey.noteVisibilities as readonly string[]).includes(visibility.value)) ? visibility.value as typeof Misskey.noteVisibilities[number] : undefined,
localOnly: localOnly?.value,
};
}
function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiPostFormButton, 'id' | 'type'> {
utils.assertObject(def);
@ -459,22 +479,11 @@ function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: valu
const form = def.value.get('form');
if (form) utils.assertObject(form);
const getForm = () => {
const text = form!.value.get('text');
utils.assertString(text);
const cw = form!.value.get('cw');
if (cw) utils.assertString(cw);
return {
text: text.value,
cw: cw?.value,
};
};
return {
text: text?.value,
primary: primary?.value,
rounded: rounded?.value,
form: form ? getForm() : {
form: form ? getPostFormProps(form) : {
text: '',
},
};
@ -486,19 +495,8 @@ function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn
const form = def.value.get('form');
if (form) utils.assertObject(form);
const getForm = () => {
const text = form!.value.get('text');
utils.assertString(text);
const cw = form!.value.get('cw');
if (cw) utils.assertString(cw);
return {
text: text.value,
cw: cw?.value,
};
};
return {
form: form ? getForm() : {
form: form ? getPostFormProps(form) : {
text: '',
},
};

View file

@ -6,15 +6,16 @@
import * as Misskey from 'misskey-js';
export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean {
const collapsed = note.cw == null && note.text != null && (
(note.text.includes('$[x2')) ||
(note.text.includes('$[x3')) ||
(note.text.includes('$[x4')) ||
(note.text.includes('$[scale')) ||
(note.text.split('\n').length > 9) ||
(note.text.length > 500) ||
(note.files.length >= 5) ||
(urls.length >= 4)
const collapsed = note.cw == null && (
note.text != null && (
(note.text.includes('$[x2')) ||
(note.text.includes('$[x3')) ||
(note.text.includes('$[x4')) ||
(note.text.includes('$[scale')) ||
(note.text.split('\n').length > 9) ||
(note.text.length > 500) ||
(urls.length >= 4)
) || note.files.length >= 5
);
return collapsed;

View file

@ -3,18 +3,22 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as Misskey from 'misskey-js';
type EnumItem = string | {
label: string;
value: string;
};
type Hidden = boolean | ((v: any) => boolean);
export type FormItem = {
label?: string;
type: 'string';
default: string | null;
description?: string;
required?: boolean;
hidden?: boolean;
hidden?: Hidden;
multiline?: boolean;
treatAsMfm?: boolean;
} | {
@ -23,27 +27,27 @@ export type FormItem = {
default: number | null;
description?: string;
required?: boolean;
hidden?: boolean;
hidden?: Hidden;
step?: number;
} | {
label?: string;
type: 'boolean';
default: boolean | null;
description?: string;
hidden?: boolean;
hidden?: Hidden;
} | {
label?: string;
type: 'enum';
default: string | null;
required?: boolean;
hidden?: boolean;
hidden?: Hidden;
enum: EnumItem[];
} | {
label?: string;
type: 'radio';
default: unknown | null;
required?: boolean;
hidden?: boolean;
hidden?: Hidden;
options: {
label: string;
value: unknown;
@ -58,20 +62,27 @@ export type FormItem = {
min: number;
max: number;
textConverter?: (value: number) => string;
hidden?: Hidden;
} | {
label?: string;
type: 'object';
default: Record<string, unknown> | null;
hidden: boolean;
hidden: Hidden;
} | {
label?: string;
type: 'array';
default: unknown[] | null;
hidden: boolean;
hidden: Hidden;
} | {
type: 'button';
content?: string;
hidden?: Hidden;
action: (ev: MouseEvent, v: any) => void;
} | {
type: 'drive-file';
defaultFileId?: string | null;
hidden?: Hidden;
validate?: (v: Misskey.entities.DriveFile) => Promise<boolean>;
};
export type Form = Record<string, FormItem>;
@ -84,8 +95,9 @@ type GetItemType<Item extends FormItem> =
Item['type'] extends 'range' ? number :
Item['type'] extends 'enum' ? string :
Item['type'] extends 'array' ? unknown[] :
Item['type'] extends 'object' ? Record<string, unknown>
: never;
Item['type'] extends 'object' ? Record<string, unknown> :
Item['type'] extends 'drive-file' ? Misskey.entities.DriveFile | undefined :
never;
export type GetFormResultType<F extends Form> = {
[P in keyof F]: GetItemType<F[P]>;

View file

@ -16,7 +16,7 @@ import { url } from '@/config.js';
import { defaultStore, noteActions } from '@/store.js';
import { miLocalStorage } from '@/local-storage.js';
import { getUserMenu } from '@/scripts/get-user-menu.js';
import { clipsCache } from '@/cache.js';
import { clipsCache, favoritedChannelsCache } from '@/cache.js';
import { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { isSupportShare } from '@/scripts/navigator.js';
@ -492,10 +492,9 @@ export function getNoteMenu(props: {
};
}
type Visibility = 'public' | 'home' | 'followers' | 'specified';
type Visibility = (typeof Misskey.noteVisibilities)[number];
// defaultStore.state.visibilityがstringなためstringも受け付けている
function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility {
function smallerVisibility(a: Visibility, b: Visibility): Visibility {
if (a === 'specified' || b === 'specified') return 'specified';
if (a === 'followers' || b === 'followers') return 'followers';
if (a === 'home' || b === 'home') return 'home';
@ -519,6 +518,7 @@ export function getRenoteMenu(props: {
const channelRenoteItems: MenuItem[] = [];
const normalRenoteItems: MenuItem[] = [];
const normalExternalChannelRenoteItems: MenuItem[] = [];
if (appearNote.channel) {
channelRenoteItems.push(...[{
@ -597,12 +597,47 @@ export function getRenoteMenu(props: {
});
},
}]);
normalExternalChannelRenoteItems.push({
type: 'parent',
icon: 'ti ti-repeat',
text: appearNote.channel ? i18n.ts.renoteToOtherChannel : i18n.ts.renoteToChannel,
children: async () => {
const channels = await favoritedChannelsCache.fetch();
return channels.filter((channel) => {
if (!appearNote.channelId) return true;
return channel.id !== appearNote.channelId;
}).map((channel) => ({
text: channel.name,
action: () => {
const el = props.renoteButton.value;
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
if (!props.mock) {
misskeyApi('notes/create', {
renoteId: appearNote.id,
channelId: channel.id,
}).then(() => {
os.toast(i18n.tsx.renotedToX({ name: channel.name }));
});
}
},
}));
},
});
}
const renoteItems = [
...normalRenoteItems,
...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] as MenuItem[] : [],
...channelRenoteItems,
...(normalExternalChannelRenoteItems.length > 0 && (normalRenoteItems.length > 0 || channelRenoteItems.length > 0)) ? [{ type: 'divider' }] as MenuItem[] : [],
...normalExternalChannelRenoteItems,
];
return {

View file

@ -272,7 +272,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
text: r.name,
action: async () => {
const { canceled, result: period } = await os.select({
title: i18n.ts.period,
title: i18n.ts.period + ': ' + r.name,
items: [{
value: 'indefinitely', text: i18n.ts.indefinitely,
}, {