summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2024-11-11 16:35:23 +0900
committersyuilo <4439005+syuilo@users.noreply.github.com>2024-11-11 16:35:23 +0900
commit1bc4f400c0efc4a9430a7bbb1a7268aff2a6c455 (patch)
tree486455bb779a168fd06e2c4758489fb51eb8b865 /packages/frontend/src
parentUpdate about-misskey.vue (diff)
parentUpdate CHANGELOG.md (diff)
downloadmisskey-1bc4f400c0efc4a9430a7bbb1a7268aff2a6c455.tar.gz
misskey-1bc4f400c0efc4a9430a7bbb1a7268aff2a6c455.tar.bz2
misskey-1bc4f400c0efc4a9430a7bbb1a7268aff2a6c455.zip
Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/boot/common.ts6
-rw-r--r--packages/frontend/src/components/MkMediaVideo.vue43
-rw-r--r--packages/frontend/src/components/MkNote.vue15
-rw-r--r--packages/frontend/src/components/MkPostForm.vue6
-rw-r--r--packages/frontend/src/components/MkSignupDialog.form.vue4
-rw-r--r--packages/frontend/src/components/MkTimeline.vue1
-rw-r--r--packages/frontend/src/pages/announcement.vue2
-rw-r--r--packages/frontend/src/pages/auth.vue2
-rw-r--r--packages/frontend/src/pages/miauth.vue2
-rw-r--r--packages/frontend/src/pages/timeline.vue2
-rw-r--r--packages/frontend/src/pizzax.ts12
-rw-r--r--packages/frontend/src/scripts/device-kind.ts24
-rw-r--r--packages/frontend/src/scripts/fullscreen.ts46
-rw-r--r--packages/frontend/src/store.ts9
14 files changed, 116 insertions, 58 deletions
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 90ae49ee59..bfe5c4f5f7 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -15,7 +15,7 @@ import { updateI18n, i18n } from '@/i18n.js';
import { $i, refreshAccount, login } from '@/account.js';
import { defaultStore, ColdDeviceStorage } from '@/store.js';
import { fetchInstance, instance } from '@/instance.js';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { deviceKind, updateDeviceKind } from '@/scripts/device-kind.js';
import { reloadChannel } from '@/scripts/unison-reload.js';
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
@@ -185,6 +185,10 @@ export async function common(createVue: () => App<Element>) {
}
});
+ watch(defaultStore.reactiveState.overridedDeviceKind, (kind) => {
+ updateDeviceKind(kind);
+ }, { immediate: true });
+
watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
}, { immediate: true });
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index d3a12ca734..65e4a1eb12 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -118,7 +118,7 @@ import { hms } from '@/filters/hms.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { isFullscreenNotSupported } from '@/scripts/device-kind.js';
+import { exitFullscreen, requestFullscreen } from '@/scripts/fullscreen.js';
import hasAudio from '@/scripts/media-has-audio.js';
import MkMediaRange from '@/components/MkMediaRange.vue';
import { $i, iAmModerator } from '@/account.js';
@@ -334,26 +334,21 @@ function togglePlayPause() {
}
function toggleFullscreen() {
- if (isFullscreenNotSupported && videoEl.value) {
- if (isFullscreen.value) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- videoEl.value.webkitExitFullscreen();
- isFullscreen.value = false;
- } else {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- videoEl.value.webkitEnterFullscreen();
- isFullscreen.value = true;
- }
- } else if (playerEl.value) {
- if (isFullscreen.value) {
- document.exitFullscreen();
- isFullscreen.value = false;
- } else {
- playerEl.value.requestFullscreen({ navigationUI: 'hide' });
- isFullscreen.value = true;
- }
+ if (playerEl.value == null || videoEl.value == null) return;
+ if (isFullscreen.value) {
+ exitFullscreen({
+ videoEl: videoEl.value,
+ });
+ isFullscreen.value = false;
+ } else {
+ requestFullscreen({
+ videoEl: videoEl.value,
+ playerEl: playerEl.value,
+ options: {
+ navigationUI: 'hide',
+ },
+ });
+ isFullscreen.value = true;
}
}
@@ -454,8 +449,10 @@ watch(loop, (to) => {
});
watch(hide, (to) => {
- if (to && isFullscreen.value) {
- document.exitFullscreen();
+ if (videoEl.value && to && isFullscreen.value) {
+ exitFullscreen({
+ videoEl: videoEl.value,
+ });
isFullscreen.value = false;
}
});
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 3de69d6d09..cf0d0787b1 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -292,15 +292,18 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
*/
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
- if (mutedWords == null) return false;
-
- if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
- if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
- if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
+ if (mutedWords != null) {
+ if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
+ if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
+ if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
+ }
if (checkOnly) return false;
- if (inTimeline && !tl_withSensitive.value && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute';
+ if (inTimeline && tl_withSensitive.value === false && noteToCheck.files?.some((v) => v.isSensitive)) {
+ return 'sensitiveMute';
+ }
+
return false;
}
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index f2fe048449..0b5794d1e3 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -1108,7 +1108,7 @@ defineExpose({
&:focus-visible {
outline: none;
- .submitInner {
+ > .submitInner {
outline: 2px solid var(--MI_THEME-fgOnAccent);
outline-offset: -4px;
}
@@ -1123,13 +1123,13 @@ defineExpose({
}
&:not(:disabled):hover {
- > .inner {
+ > .submitInner {
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
}
}
&:not(:disabled):active {
- > .inner {
+ > .submitInner {
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
}
}
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index 3d1c44fc90..e1f4e26d62 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -277,7 +277,7 @@ async function onSubmit(): Promise<void> {
return null;
});
- if (res) {
+ if (res && res.ok) {
if (res.status === 204 || instance.emailRequiredForSignup) {
os.alert({
type: 'success',
@@ -295,6 +295,8 @@ async function onSubmit(): Promise<void> {
await login(resJson.token);
}
}
+ } else {
+ onSignupApiError();
}
submitting.value = false;
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 226faac291..fb8eb4ae37 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -43,6 +43,7 @@ const props = withDefaults(defineProps<{
}>(), {
withRenotes: true,
withReplies: false,
+ withSensitive: true,
onlyFiles: false,
});
diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue
index 01c29cf02d..56c10fb292 100644
--- a/packages/frontend/src/pages/announcement.vue
+++ b/packages/frontend/src/pages/announcement.vue
@@ -103,7 +103,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata(() => ({
- title: announcement.value ? `${i18n.ts.announcements}: ${announcement.value.title}` : i18n.ts.announcements,
+ title: announcement.value ? announcement.value.title : i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));
</script>
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index d8f8d0b428..4170b4f73e 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -62,7 +62,7 @@ function accepted() {
state.value = 'accepted';
if (session.value && session.value.app.callbackUrl) {
const url = new URL(session.value.app.callbackUrl);
- if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
+ if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(url.protocol)) throw new Error('invalid url');
location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`;
}
}
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index e89dd5c4a5..e85d2c29c1 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -65,7 +65,7 @@ async function onAccept(token: string) {
if (props.callback && props.callback !== '') {
const cbUrl = new URL(props.callback);
- if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url');
+ if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url');
cbUrl.searchParams.set('session', props.session);
location.href = cbUrl.toString();
} else {
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 7a3195304b..044a1908ab 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.tl">
<MkTimeline
ref="tlComponent"
- :key="src + withRenotes + withReplies + onlyFiles"
+ :key="src + withRenotes + withReplies + onlyFiles + withSensitive"
:src="src.split(':')[0]"
:list="src.split(':')[1]"
:withRenotes="withRenotes"
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index ac325e923f..7740fe0d39 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -241,9 +241,13 @@ export class Storage<T extends StateDef> {
* 特定のキーの、簡易的なgetter/setterを作ります
* 主にvue上で設定コントロールのmodelとして使う用
*/
- public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): {
- get: () => T[K]['default'];
- set: (value: T[K]['default']) => void;
+ public makeGetterSetter<K extends keyof T, R = T[K]['default']>(
+ key: K,
+ getter?: (v: T[K]['default']) => R,
+ setter?: (v: R) => T[K]['default'],
+ ): {
+ get: () => R;
+ set: (value: R) => void;
} {
const valueRef = ref(this.state[key]);
@@ -265,7 +269,7 @@ export class Storage<T extends StateDef> {
return valueRef.value;
}
},
- set: (value: unknown) => {
+ set: (value) => {
const val = setter ? setter(value) : value;
this.set(key, val);
valueRef.value = val;
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/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/store.ts b/packages/frontend/src/store.ts
index 911a463636..1d981e897b 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -8,8 +8,9 @@ import * as Misskey from 'misskey-js';
import { hemisphere } from '@@/js/intl-const.js';
import lightTheme from '@@/themes/l-light.json5';
import darkTheme from '@@/themes/d-green-lime.json5';
-import { miLocalStorage } from './local-storage.js';
import type { SoundType } from '@/scripts/sound.js';
+import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js';
+import { miLocalStorage } from '@/local-storage.js';
import { Storage } from '@/pizzax.js';
import type { Ast } from '@syuilo/aiscript';
@@ -207,7 +208,7 @@ export const defaultStore = markRaw(new Storage('base', {
overridedDeviceKind: {
where: 'device',
- default: null as null | 'smartphone' | 'tablet' | 'desktop',
+ default: null as DeviceKind | null,
},
serverDisconnectedBehavior: {
where: 'device',
@@ -263,11 +264,11 @@ export const defaultStore = markRaw(new Storage('base', {
},
useBlurEffectForModal: {
where: 'device',
- default: !/mobile|iphone|android/.test(navigator.userAgent.toLowerCase()), // 循環参照するのでdevice-kind.tsは参照できない
+ default: DEFAULT_DEVICE_KIND === 'desktop',
},
useBlurEffect: {
where: 'device',
- default: !/mobile|iphone|android/.test(navigator.userAgent.toLowerCase()), // 循環参照するのでdevice-kind.tsは参照できない
+ default: DEFAULT_DEVICE_KIND === 'desktop',
},
showFixedPostForm: {
where: 'device',