Merge tag '2024.11.0' into feature/2024.10
This commit is contained in:
commit
bc816cb166
234 changed files with 6612 additions and 4634 deletions
|
|
@ -2,10 +2,9 @@
|
|||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import * as Misskey from 'misskey-js';
|
||||
|
||||
import type { Note, MeDetailed } from "misskey-js/entities.js";
|
||||
|
||||
export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mutedWords: Array<string | string[]>): boolean {
|
||||
export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): boolean {
|
||||
// 自分自身
|
||||
if (me && (note.userId === me.id)) return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,22 +3,22 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
||||
await defaultStore.ready;
|
||||
export type DeviceKind = 'smartphone' | 'tablet' | 'desktop';
|
||||
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700);
|
||||
const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua);
|
||||
|
||||
const isIPhone = /iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
|
||||
// navigator.platform may be deprecated but this check is still required
|
||||
const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
|
||||
const isIos = /ipad|iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
|
||||
export const DEFAULT_DEVICE_KIND: DeviceKind = (
|
||||
isSmartphone
|
||||
? 'smartphone'
|
||||
: isTablet
|
||||
? 'tablet'
|
||||
: 'desktop'
|
||||
);
|
||||
|
||||
export const isFullscreenNotSupported = isIPhone || isIos;
|
||||
export let deviceKind: DeviceKind = DEFAULT_DEVICE_KIND;
|
||||
|
||||
export const deviceKind: 'smartphone' | 'tablet' | 'desktop' = defaultStore.state.overridedDeviceKind ? defaultStore.state.overridedDeviceKind
|
||||
: isSmartphone ? 'smartphone'
|
||||
: isTablet ? 'tablet'
|
||||
: 'desktop';
|
||||
export function updateDeviceKind(kind: DeviceKind | null) {
|
||||
deviceKind = kind ?? DEFAULT_DEVICE_KIND;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ type Hidden = boolean | ((v: any) => boolean);
|
|||
export type FormItem = {
|
||||
label?: string;
|
||||
type: 'string';
|
||||
default: string | null;
|
||||
default?: string | null;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
hidden?: Hidden;
|
||||
|
|
@ -24,7 +24,7 @@ export type FormItem = {
|
|||
} | {
|
||||
label?: string;
|
||||
type: 'number';
|
||||
default: number | null;
|
||||
default?: number | null;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
hidden?: Hidden;
|
||||
|
|
@ -32,20 +32,20 @@ export type FormItem = {
|
|||
} | {
|
||||
label?: string;
|
||||
type: 'boolean';
|
||||
default: boolean | null;
|
||||
default?: boolean | null;
|
||||
description?: string;
|
||||
hidden?: Hidden;
|
||||
} | {
|
||||
label?: string;
|
||||
type: 'enum';
|
||||
default: string | null;
|
||||
default?: string | null;
|
||||
required?: boolean;
|
||||
hidden?: Hidden;
|
||||
enum: EnumItem[];
|
||||
} | {
|
||||
label?: string;
|
||||
type: 'radio';
|
||||
default: unknown | null;
|
||||
default?: unknown | null;
|
||||
required?: boolean;
|
||||
hidden?: Hidden;
|
||||
options: {
|
||||
|
|
@ -55,7 +55,7 @@ export type FormItem = {
|
|||
} | {
|
||||
label?: string;
|
||||
type: 'range';
|
||||
default: number | null;
|
||||
default?: number | null;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
step?: number;
|
||||
|
|
@ -66,12 +66,12 @@ export type FormItem = {
|
|||
} | {
|
||||
label?: string;
|
||||
type: 'object';
|
||||
default: Record<string, unknown> | null;
|
||||
default?: Record<string, unknown> | null;
|
||||
hidden: Hidden;
|
||||
} | {
|
||||
label?: string;
|
||||
type: 'array';
|
||||
default: unknown[] | null;
|
||||
default?: unknown[] | null;
|
||||
hidden: Hidden;
|
||||
} | {
|
||||
type: 'button';
|
||||
|
|
|
|||
46
packages/frontend/src/scripts/fullscreen.ts
Normal file
46
packages/frontend/src/scripts/fullscreen.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
type PartiallyPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||
|
||||
type VideoEl = PartiallyPartial<HTMLVideoElement, 'requestFullscreen'> & {
|
||||
webkitEnterFullscreen?(): void;
|
||||
webkitExitFullscreen?(): void;
|
||||
};
|
||||
|
||||
type PlayerEl = PartiallyPartial<HTMLElement, 'requestFullscreen'>;
|
||||
|
||||
type RequestFullscreenProps = {
|
||||
readonly videoEl: VideoEl;
|
||||
readonly playerEl: PlayerEl;
|
||||
readonly options?: FullscreenOptions | null;
|
||||
};
|
||||
|
||||
type ExitFullscreenProps = {
|
||||
readonly videoEl: VideoEl;
|
||||
};
|
||||
|
||||
export const requestFullscreen = ({ videoEl, playerEl, options }: RequestFullscreenProps) => {
|
||||
if (playerEl.requestFullscreen != null) {
|
||||
playerEl.requestFullscreen(options ?? undefined);
|
||||
return;
|
||||
}
|
||||
if (videoEl.webkitEnterFullscreen != null) {
|
||||
videoEl.webkitEnterFullscreen();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export const exitFullscreen = ({ videoEl }: ExitFullscreenProps) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (document.exitFullscreen != null) {
|
||||
document.exitFullscreen();
|
||||
return;
|
||||
}
|
||||
if (videoEl.webkitExitFullscreen != null) {
|
||||
videoEl.webkitExitFullscreen();
|
||||
return;
|
||||
}
|
||||
};
|
||||
18
packages/frontend/src/scripts/get-bg-color.ts
Normal file
18
packages/frontend/src/scripts/get-bg-color.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
export const getBgColor = (elem?: Element | null | undefined): string | null => {
|
||||
if (elem == null) return null;
|
||||
|
||||
const { backgroundColor: bg } = window.getComputedStyle(elem);
|
||||
|
||||
if (bg && tinycolor(bg).getAlpha() !== 0) {
|
||||
return bg;
|
||||
}
|
||||
|
||||
return getBgColor(elem.parentElement);
|
||||
};
|
||||
|
|
@ -17,7 +17,7 @@ export function misskeyApi<
|
|||
_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
|
||||
>(
|
||||
endpoint: E,
|
||||
data: P = {} as any,
|
||||
data: P & { i?: string | null; } = {} as any,
|
||||
token?: string | null | undefined,
|
||||
signal?: AbortSignal,
|
||||
): Promise<_ResT> {
|
||||
|
|
@ -30,8 +30,8 @@ export function misskeyApi<
|
|||
|
||||
const promise = new Promise<_ResT>((resolve, reject) => {
|
||||
// Append a credential
|
||||
if ($i) (data as any).i = $i.token;
|
||||
if (token !== undefined) (data as any).i = token;
|
||||
if ($i) data.i = $i.token;
|
||||
if (token !== undefined) data.i = token;
|
||||
|
||||
// Send request
|
||||
window.fetch(`${apiUrl}/${endpoint}`, {
|
||||
|
|
|
|||
|
|
@ -44,17 +44,21 @@ export type OpenOnRemoteOptions = {
|
|||
params: Record<string, string>;
|
||||
};
|
||||
|
||||
export function pleaseLogin(path?: string, openOnRemote?: OpenOnRemoteOptions) {
|
||||
export function pleaseLogin(opts: {
|
||||
path?: string;
|
||||
message?: string;
|
||||
openOnRemote?: OpenOnRemoteOptions;
|
||||
} = {}) {
|
||||
if ($i) return;
|
||||
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {
|
||||
autoSet: true,
|
||||
message: openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired,
|
||||
openOnRemote,
|
||||
message: opts.message ?? (opts.openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired),
|
||||
openOnRemote: opts.openOnRemote,
|
||||
}, {
|
||||
cancelled: () => {
|
||||
if (path) {
|
||||
window.location.href = path;
|
||||
if (opts.path) {
|
||||
window.location.href = opts.path;
|
||||
}
|
||||
},
|
||||
closed: () => dispose(),
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
|
|||
});
|
||||
}
|
||||
|
||||
function select(src: any, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||
return new Promise((res, rej) => {
|
||||
const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
|
||||
|
||||
|
|
@ -107,10 +107,10 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Miss
|
|||
});
|
||||
}
|
||||
|
||||
export function selectFile(src: any, label: string | null = null): Promise<Misskey.entities.DriveFile> {
|
||||
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
|
||||
return select(src, label, false).then(files => files[0]);
|
||||
}
|
||||
|
||||
export function selectFiles(src: any, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
|
||||
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
|
||||
return select(src, label, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
/**
|
||||
* 配列をシャッフル (破壊的)
|
||||
*/
|
||||
export function shuffle<T extends any[]>(array: T): T {
|
||||
let currentIndex = array.length, randomIndex;
|
||||
export function shuffle<T extends unknown[]>(array: T): T {
|
||||
let currentIndex = array.length;
|
||||
let randomIndex: number;
|
||||
|
||||
// While there remain elements to shuffle.
|
||||
while (currentIndex !== 0) {
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ const mimeTypeMap = {
|
|||
|
||||
export function uploadFile(
|
||||
file: File,
|
||||
folder?: any,
|
||||
folder?: string | Misskey.entities.DriveFolder,
|
||||
name?: string,
|
||||
keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
|
||||
): Promise<Misskey.entities.DriveFile> {
|
||||
if ($i == null) throw new Error('Not logged in');
|
||||
|
||||
if (folder && typeof folder === 'object') folder = folder.id;
|
||||
const _folder = typeof folder === 'string' ? folder : folder?.id;
|
||||
|
||||
if (file.size > instance.maxFileSize) {
|
||||
alert({
|
||||
|
|
@ -89,11 +89,11 @@ export function uploadFile(
|
|||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('i', $i.token);
|
||||
formData.append('i', $i!.token);
|
||||
formData.append('force', 'true');
|
||||
formData.append('file', resizedImage ?? file);
|
||||
formData.append('name', ctx.name);
|
||||
if (folder) formData.append('folderId', folder);
|
||||
if (_folder) formData.append('folderId', _folder);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', apiUrl + '/drive/files/create', true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue