diff options
| author | Julia <julia@insertdomain.name> | 2024-12-31 02:30:13 +0000 |
|---|---|---|
| committer | Julia <julia@insertdomain.name> | 2024-12-31 02:30:13 +0000 |
| commit | 4c0bbddd0fba7e0d76fb484312e691ee29fe5858 (patch) | |
| tree | 4bb1a3a2a79c679ac021a2199bd526be469524d4 /packages/frontend/src/scripts | |
| parent | merge: fixes for 2024.9.4 (if we want to) (!770) (diff) | |
| parent | Bump version (diff) | |
| download | sharkey-4c0bbddd0fba7e0d76fb484312e691ee29fe5858.tar.gz sharkey-4c0bbddd0fba7e0d76fb484312e691ee29fe5858.tar.bz2 sharkey-4c0bbddd0fba7e0d76fb484312e691ee29fe5858.zip | |
merge: Bump stable version (!842)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/842
Diffstat (limited to 'packages/frontend/src/scripts')
19 files changed, 258 insertions, 121 deletions
diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts index e65c327ffe..6525c207f7 100644 --- a/packages/frontend/src/scripts/check-word-mute.ts +++ b/packages/frontend/src/scripts/check-word-mute.ts @@ -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; diff --git a/packages/frontend/src/scripts/device-kind.ts b/packages/frontend/src/scripts/device-kind.ts index 7c33f8ccee..7aadb617ca 100644 --- a/packages/frontend/src/scripts/device-kind.ts +++ b/packages/frontend/src/scripts/device-kind.ts @@ -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; +} diff --git a/packages/frontend/src/scripts/favicon-dot.ts b/packages/frontend/src/scripts/favicon-dot.ts index a5972276e2..1f62c9ca70 100644 --- a/packages/frontend/src/scripts/favicon-dot.ts +++ b/packages/frontend/src/scripts/favicon-dot.ts @@ -78,7 +78,7 @@ class FavIconDot { this.ctx.beginPath(); this.ctx.arc(this.faviconImage.width - 10, 10, 10, 0, 2 * Math.PI); const computedStyle = getComputedStyle(document.documentElement); - this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--navIndicator')).toHexString(); + this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--MI_THEME-navIndicator')).toHexString(); this.ctx.strokeStyle = 'white'; this.ctx.fill(); this.ctx.stroke(); @@ -104,7 +104,7 @@ class FavIconDot { this.drawDot(); this.canvas.toDataURL('image/png'); } catch (error) { - return false; + return false; } return true; } @@ -140,6 +140,6 @@ export async function worksOnInstance() { icon = new FavIconDot(); await icon.setup(); } - + return await icon.worksOnInstance(); } diff --git a/packages/frontend/src/scripts/following-feed-utils.ts b/packages/frontend/src/scripts/following-feed-utils.ts index 064d6b72e3..39f17949d6 100644 --- a/packages/frontend/src/scripts/following-feed-utils.ts +++ b/packages/frontend/src/scripts/following-feed-utils.ts @@ -3,19 +3,75 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { computed } from 'vue'; +import { computed, Ref, WritableComputedRef } from 'vue'; import { defaultStore } from '@/store.js'; import { deepMerge } from '@/scripts/merge.js'; import { PageHeaderItem } from '@/types/page-header.js'; import { i18n } from '@/i18n.js'; import { popupMenu } from '@/os.js'; +import { MenuItem } from '@/types/menu.js'; export const followingTab = 'following' as const; export const mutualsTab = 'mutuals' as const; export const followersTab = 'followers' as const; -export type FollowingFeedTab = typeof followingTab | typeof mutualsTab | typeof followersTab; +export const followingFeedTabs = [followingTab, mutualsTab, followersTab] as const; +export type FollowingFeedTab = typeof followingFeedTabs[number]; -export function createOptions(): PageHeaderItem { +export function followingTabName(tab: FollowingFeedTab): string; +export function followingTabName(tab: FollowingFeedTab | null | undefined): null; +export function followingTabName(tab: FollowingFeedTab | null | undefined): string | null { + if (tab === followingTab) return i18n.ts.following; + if (tab === followersTab) return i18n.ts.followers; + if (tab === mutualsTab) return i18n.ts.mutuals; + return null; +} + +export function followingTabIcon(tab: FollowingFeedTab | null | undefined): string { + if (tab === followersTab) return 'ph-user ph-bold ph-lg'; + if (tab === mutualsTab) return 'ph-user-switch ph-bold ph-lg'; + return 'ph-user-check ph-bold ph-lg'; +} + +export type FollowingFeedModel = { + [Key in keyof FollowingFeedState]: WritableComputedRef<FollowingFeedState[Key]>; +} + +export interface FollowingFeedState { + withNonPublic: boolean, + withQuotes: boolean, + withBots: boolean, + withReplies: boolean, + onlyFiles: boolean, + userList: FollowingFeedTab, + remoteWarningDismissed: boolean, +} + +export const defaultFollowingFeedState: FollowingFeedState = { + withNonPublic: false, + withQuotes: false, + withBots: true, + withReplies: false, + onlyFiles: false, + userList: followingTab, + remoteWarningDismissed: false, +}; + +interface StorageInterface<T extends Partial<FollowingFeedState> = Partial<FollowingFeedState>> { + readonly state: Partial<T>; + readonly reactiveState: Ref<Partial<T>>; + save(updated: T): void; +} + +export function createHeaderItem(storage?: Ref<StorageInterface>): PageHeaderItem { + const menu = createOptionsMenu(storage); + return { + icon: 'ti ti-dots', + text: i18n.ts.options, + handler: ev => popupMenu(menu, ev.currentTarget ?? ev.target), + }; +} + +export function createOptionsMenu(storage?: Ref<StorageInterface>): MenuItem[] { const { userList, withNonPublic, @@ -23,80 +79,83 @@ export function createOptions(): PageHeaderItem { withBots, withReplies, onlyFiles, - } = createModel(); + } = createModel(storage); - return { - icon: 'ti ti-dots', - text: i18n.ts.options, - handler: ev => - popupMenu([ - { - type: 'switch', - text: i18n.ts.showNonPublicNotes, - ref: withNonPublic, - disabled: userList.value === 'followers', - }, - { - type: 'switch', - text: i18n.ts.showQuotes, - ref: withQuotes, - }, - { - type: 'switch', - text: i18n.ts.showBots, - ref: withBots, - }, - { - type: 'switch', - text: i18n.ts.showReplies, - ref: withReplies, - disabled: onlyFiles, - }, - { - type: 'divider', - }, - { - type: 'switch', - text: i18n.ts.fileAttachedOnly, - ref: onlyFiles, - disabled: withReplies, - }, - ], ev.currentTarget ?? ev.target), - }; + return [ + { + type: 'switch', + text: i18n.ts.showNonPublicNotes, + ref: withNonPublic, + disabled: computed(() => userList.value === followersTab), + }, + { + type: 'switch', + text: i18n.ts.showQuotes, + ref: withQuotes, + }, + { + type: 'switch', + text: i18n.ts.showBots, + ref: withBots, + }, + { + type: 'switch', + text: i18n.ts.showReplies, + ref: withReplies, + disabled: onlyFiles, + }, + { + type: 'divider', + }, + { + type: 'switch', + text: i18n.ts.fileAttachedOnly, + ref: onlyFiles, + disabled: withReplies, + }, + ]; } -export function createModel() { - const userList = computed({ - get: () => defaultStore.reactiveState.followingFeed.value.userList, +export function createModel(storage?: Ref<StorageInterface>): FollowingFeedModel { + // eslint-disable-next-line no-param-reassign + storage ??= createDefaultStorage(); + + // Based on timeline.saveTlFilter() + const saveFollowingFilter = <K extends keyof FollowingFeedState>(key: K, value: FollowingFeedState[K]) => { + const state = deepMerge(storage.value.state, defaultFollowingFeedState); + const out = deepMerge({ [key]: value }, state); + storage.value.save(out); + }; + + const userList: WritableComputedRef<FollowingFeedTab> = computed({ + get: () => storage.value.reactiveState.value.userList ?? defaultFollowingFeedState.userList, set: value => saveFollowingFilter('userList', value), }); - - const withNonPublic = computed({ + const withNonPublic: WritableComputedRef<boolean> = computed({ get: () => { if (userList.value === 'followers') return false; - return defaultStore.reactiveState.followingFeed.value.withNonPublic; + return storage.value.reactiveState.value.withNonPublic ?? defaultFollowingFeedState.withNonPublic; }, set: value => saveFollowingFilter('withNonPublic', value), }); - const withQuotes = computed({ - get: () => defaultStore.reactiveState.followingFeed.value.withQuotes, + const withQuotes: WritableComputedRef<boolean> = computed({ + get: () => storage.value.reactiveState.value.withQuotes ?? defaultFollowingFeedState.withQuotes, set: value => saveFollowingFilter('withQuotes', value), }); - const withBots = computed({ - get: () => defaultStore.reactiveState.followingFeed.value.withBots, + const withBots: WritableComputedRef<boolean> = computed({ + get: () => storage.value.reactiveState.value.withBots ?? defaultFollowingFeedState.withBots, set: value => saveFollowingFilter('withBots', value), }); - const withReplies = computed({ - get: () => defaultStore.reactiveState.followingFeed.value.withReplies, + const withReplies: WritableComputedRef<boolean> = computed({ + get: () => storage.value.reactiveState.value.withReplies ?? defaultFollowingFeedState.withReplies, set: value => saveFollowingFilter('withReplies', value), }); - const onlyFiles = computed({ - get: () => defaultStore.reactiveState.followingFeed.value.onlyFiles, + const onlyFiles: WritableComputedRef<boolean> = computed({ + get: () => storage.value.reactiveState.value.onlyFiles ?? defaultFollowingFeedState.onlyFiles, set: value => saveFollowingFilter('onlyFiles', value), }); - - const remoteWarningDismissed = computed({ - get: () => defaultStore.reactiveState.followingFeed.value.remoteWarningDismissed, + const remoteWarningDismissed: WritableComputedRef<boolean> = computed({ + get: () => storage.value.reactiveState.value.remoteWarningDismissed ?? defaultFollowingFeedState.remoteWarningDismissed, set: value => saveFollowingFilter('remoteWarningDismissed', value), }); @@ -111,8 +170,12 @@ export function createModel() { }; } -// Based on timeline.saveTlFilter() -function saveFollowingFilter<Key extends keyof typeof defaultStore.state.followingFeed>(key: Key, value: (typeof defaultStore.state.followingFeed)[Key]) { - const out = deepMerge({ [key]: value }, defaultStore.state.followingFeed); - return defaultStore.set('followingFeed', out); +function createDefaultStorage() { + return computed(() => ({ + state: defaultStore.state.followingFeed, + reactiveState: defaultStore.reactiveState.followingFeed, + save(updated: typeof defaultStore.state.followingFeed) { + return defaultStore.set('followingFeed', updated); + }, + })); } diff --git a/packages/frontend/src/scripts/form.ts b/packages/frontend/src/scripts/form.ts index 242a504c3b..1032e97ac9 100644 --- a/packages/frontend/src/scripts/form.ts +++ b/packages/frontend/src/scripts/form.ts @@ -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'; diff --git a/packages/frontend/src/scripts/fullscreen.ts b/packages/frontend/src/scripts/fullscreen.ts new file mode 100644 index 0000000000..7a0a018ef3 --- /dev/null +++ b/packages/frontend/src/scripts/fullscreen.ts @@ -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; + } +}; diff --git a/packages/frontend/src/scripts/get-bg-color.ts b/packages/frontend/src/scripts/get-bg-color.ts new file mode 100644 index 0000000000..ccf60b454f --- /dev/null +++ b/packages/frontend/src/scripts/get-bg-color.ts @@ -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); +}; diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index 4f3fb65665..c56fd185b6 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -267,13 +267,10 @@ export function getNoteMenu(props: { function togglePin(pin: boolean): void { os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { noteId: appearNote.id, - }, undefined, null, res => { - if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: i18n.ts.pinLimitExceeded, - }); - } + }, undefined, { + '72dab508-c64d-498f-8740-a8eec1ba385a': { + text: i18n.ts.pinLimitExceeded, + }, }); } diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index d15279d633..090cffe203 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -103,8 +103,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter async function getConfirmed(text: string): Promise<boolean> { const confirm = await os.confirm({ - type: 'warning', - title: 'confirm', + type: 'question', text, }); diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts index 04fb235694..d5304ee210 100644 --- a/packages/frontend/src/scripts/hotkey.ts +++ b/packages/frontend/src/scripts/hotkey.ts @@ -42,7 +42,7 @@ const KEY_ALIASES = { const MODIFIER_KEYS = ['ctrl', 'alt', 'shift']; -const IGNORE_ELEMENTS = ['input', 'textarea']; +const IGNORE_ELEMENTS = ['input', 'textarea', 'ruffle-player']; //#endregion //#region store diff --git a/packages/frontend/src/scripts/init-chart.ts b/packages/frontend/src/scripts/init-chart.ts index 2465a14703..41e1636aa7 100644 --- a/packages/frontend/src/scripts/init-chart.ts +++ b/packages/frontend/src/scripts/init-chart.ts @@ -50,7 +50,7 @@ export function initChart() { ); // フォントカラー - Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); + Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg'); Chart.defaults.borderColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts index 9794a300da..89fdda0cbb 100644 --- a/packages/frontend/src/scripts/merge.ts +++ b/packages/frontend/src/scripts/merge.ts @@ -18,7 +18,7 @@ function isPureObject(value: unknown): value is Record<string | number | symbol, * valueにないキーをdefからもらう(再帰的)\ * nullはそのまま、undefinedはdefの値 **/ -export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: DeepPartial<X>, def: X): X { +export function deepMerge<X extends object>(value: DeepPartial<X>, def: X): X { if (isPureObject(value) && isPureObject(def)) { const result = deepClone(value as Cloneable) as X; for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts index 1b1159fd01..e7a92e2d5c 100644 --- a/packages/frontend/src/scripts/misskey-api.ts +++ b/packages/frontend/src/scripts/misskey-api.ts @@ -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}`, { diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts index 18f05bc7f4..43dcf11936 100644 --- a/packages/frontend/src/scripts/please-login.ts +++ b/packages/frontend/src/scripts/please-login.ts @@ -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(), diff --git a/packages/frontend/src/scripts/popup-position.ts b/packages/frontend/src/scripts/popup-position.ts index 3dad41a8b3..be49532cf8 100644 --- a/packages/frontend/src/scripts/popup-position.ts +++ b/packages/frontend/src/scripts/popup-position.ts @@ -15,6 +15,8 @@ export function calcPopupPosition(el: HTMLElement, props: { const contentWidth = el.offsetWidth; const contentHeight = el.offsetHeight; + const HORIZONTAL_MARGIN = 1; + let rect: DOMRect; if (props.anchorElement) { @@ -36,9 +38,11 @@ export function calcPopupPosition(el: HTMLElement, props: { left -= (el.offsetWidth / 2); if (left + contentWidth - window.scrollX > window.innerWidth) { - left = window.innerWidth - contentWidth + window.scrollX - 1; + left = window.innerWidth - contentWidth + window.scrollX - HORIZONTAL_MARGIN; } + left = Math.max(HORIZONTAL_MARGIN, left); + return [left, top]; }; @@ -57,9 +61,11 @@ export function calcPopupPosition(el: HTMLElement, props: { left -= (el.offsetWidth / 2); if (left + contentWidth - window.scrollX > window.innerWidth) { - left = window.innerWidth - contentWidth + window.scrollX - 1; + left = window.innerWidth - contentWidth + window.scrollX - HORIZONTAL_MARGIN; } + left = Math.max(HORIZONTAL_MARGIN, left); + return [left, top]; }; @@ -75,6 +81,8 @@ export function calcPopupPosition(el: HTMLElement, props: { top = props.y; } + left = Math.max(HORIZONTAL_MARGIN, left); + top -= (el.offsetHeight / 2); if (top + contentHeight - window.scrollY > window.innerHeight) { @@ -106,6 +114,8 @@ export function calcPopupPosition(el: HTMLElement, props: { top -= (el.offsetHeight / 2); } + left = Math.max(HORIZONTAL_MARGIN, left); + if (top + contentHeight - window.scrollY > window.innerHeight) { top = window.innerHeight - contentHeight + window.scrollY - 1; } diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts index 9aa38178b2..b037aa8acc 100644 --- a/packages/frontend/src/scripts/select-file.ts +++ b/packages/frontend/src/scripts/select-file.ts @@ -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); } diff --git a/packages/frontend/src/scripts/shuffle.ts b/packages/frontend/src/scripts/shuffle.ts index fed16bc71c..1f6ef1928c 100644 --- a/packages/frontend/src/scripts/shuffle.ts +++ b/packages/frontend/src/scripts/shuffle.ts @@ -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) { diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts index bd3cddde67..8242e7d2e4 100644 --- a/packages/frontend/src/scripts/theme.ts +++ b/packages/frontend/src/scripts/theme.ts @@ -123,7 +123,7 @@ export function applyTheme(theme: Theme, persist = true) { for (const [k, v] of Object.entries(props)) { if (k.startsWith('font')) continue; - document.documentElement.style.setProperty(`--${k}`, v.toString()); + document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); } document.documentElement.style.setProperty('color-scheme', colorScheme); diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts index 22dce609c6..713573a377 100644 --- a/packages/frontend/src/scripts/upload.ts +++ b/packages/frontend/src/scripts/upload.ts @@ -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); |