Merge branch 'develop' into chat

This commit is contained in:
syuilo
2025-03-20 16:40:21 +09:00
78 changed files with 489 additions and 382 deletions

View File

@@ -50,9 +50,40 @@ export default [
// defineExposeが誤検知されてしまう
'@typescript-eslint/no-unused-expressions': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
'id-denylist': ['error', 'window', 'e'],
// window ... グローバルスコープと衝突し、予期せぬ結果を招くため
// e ... error や event など、複数のキーワードの頭文字であり分かりにくいため
// close ... window.closeと衝突 or 紛らわしい
// open ... window.openと衝突 or 紛らわしい
// fetch ... window.fetchと衝突 or 紛らわしい
// location ... window.locationと衝突 or 紛らわしい
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location'],
'no-restricted-globals': [
'error',
{
'name': 'open',
'message': 'Use `window.open`.',
},
{
'name': 'close',
'message': 'Use `window.close`.',
},
{
'name': 'fetch',
'message': 'Use `window.fetch`.',
},
{
'name': 'location',
'message': 'Use `window.location`.',
},
{
'name': 'history',
'message': 'Use `window.history`.',
},
{
'name': 'name',
'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
},
],
'no-shadow': ['warn'],
'vue/attributes-order': ['error', {
alphabetical: false,

View File

@@ -12,9 +12,9 @@ import '@/style.scss';
import { mainBoot } from '@/boot/main-boot.js';
import { subBoot } from '@/boot/sub-boot.js';
const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete'];
const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/install-extensions'];
if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
if (subBootPaths.some(i => window.location.pathname === i || window.location.pathname.startsWith(i + '/'))) {
subBoot();
} else {
mainBoot();

View File

@@ -191,7 +191,7 @@ export async function login(token: AccountWithToken['token'], redirect?: string)
// 他のタブは再読み込みするだけ
reloadChannel.postMessage(null);
// このページはredirectで指定された先に移動
location.href = redirect;
window.location.href = redirect;
return;
}

View File

@@ -98,14 +98,14 @@ export async function common(createVue: () => App<Element>) {
document.addEventListener('touchend', () => {}, { passive: true });
// URLに#pswpを含む場合は取り除く
if (location.hash === '#pswp') {
history.replaceState(null, '', location.href.replace('#pswp', ''));
if (window.location.hash === '#pswp') {
window.history.replaceState(null, '', window.location.href.replace('#pswp', ''));
}
// 一斉リロード
reloadChannel.addEventListener('message', path => {
if (path !== null) location.href = path;
else location.reload();
if (path !== null) window.location.href = path;
else window.location.reload();
});
// If mobile, insert the viewport meta tag
@@ -130,11 +130,11 @@ export async function common(createVue: () => App<Element>) {
});
//#region loginId
const params = new URLSearchParams(location.search);
const params = new URLSearchParams(window.location.search);
const loginId = params.get('loginId');
if (loginId) {
const target = getUrlWithoutLoginId(location.href);
const target = getUrlWithoutLoginId(window.location.href);
if (!$i || $i.id !== loginId) {
const account = await getAccountFromId(loginId);
@@ -143,7 +143,7 @@ export async function common(createVue: () => App<Element>) {
}
}
history.replaceState({ misskey: 'loginId' }, '', target);
window.history.replaceState({ misskey: 'loginId' }, '', target);
}
//#endregion
@@ -300,24 +300,26 @@ export async function common(createVue: () => App<Element>) {
removeSplash();
//#region Self-XSS 対策メッセージ
console.log(
`%c${i18n.ts._selfXssPrevention.warning}`,
'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;',
);
console.log(
`%c${i18n.ts._selfXssPrevention.title}`,
'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;',
);
console.log(
`%c${i18n.ts._selfXssPrevention.description1}`,
'font-size: 16px; font-weight: 700;',
);
console.log(
`%c${i18n.ts._selfXssPrevention.description2}`,
'font-size: 16px;',
'font-size: 20px; font-weight: 700; color: #f00;',
);
console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' }));
if (!_DEV_) {
console.log(
`%c${i18n.ts._selfXssPrevention.warning}`,
'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;',
);
console.log(
`%c${i18n.ts._selfXssPrevention.title}`,
'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;',
);
console.log(
`%c${i18n.ts._selfXssPrevention.description1}`,
'font-size: 16px; font-weight: 700;',
);
console.log(
`%c${i18n.ts._selfXssPrevention.description2}`,
'font-size: 16px;',
'font-size: 20px; font-weight: 700; color: #f00;',
);
console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' }));
}
//#endregion
return {

View File

@@ -43,7 +43,7 @@ export async function mainBoot() {
if (!$i) uiStyle = 'visitor';
if (searchParams.has('zen')) uiStyle = 'zen';
if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && location.pathname !== '/') uiStyle = 'zen';
if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && window.location.pathname !== '/') uiStyle = 'zen';
if (searchParams.has('ui')) uiStyle = searchParams.get('ui');
@@ -216,7 +216,7 @@ export async function mainBoot() {
let reloadDialogShowing = false;
stream.on('_disconnected_', async () => {
if (prefer.s.serverDisconnectedBehavior === 'reload') {
location.reload();
window.location.reload();
} else if (prefer.s.serverDisconnectedBehavior === 'dialog') {
if (reloadDialogShowing) return;
reloadDialogShowing = true;
@@ -227,7 +227,7 @@ export async function mainBoot() {
});
reloadDialogShowing = false;
if (!canceled) {
location.reload();
window.location.reload();
}
}
});
@@ -458,7 +458,7 @@ export async function mainBoot() {
const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !window.location.pathname.startsWith('/miauth')) {
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {
closed: () => dispose(),

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<canvas ref="canvasEl" style="width: 100%; height: 100%; pointer-events: none;"></canvas>
<canvas ref="canvasEl" style="display: block; width: 100%; height: 100%; pointer-events: none;"></canvas>
</template>
<script lang="ts" setup>

View File

@@ -3,14 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { file } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkCropperDialog from './MkCropperDialog.vue';
import type { StoryObj } from '@storybook/vue3';
export const Default = {
render(args) {
return {
@@ -55,7 +53,7 @@ export const Default = {
http.get('/proxy/image.webp', async ({ request }) => {
const url = new URL(request.url).searchParams.get('url');
if (url === 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true') {
const image = await (await fetch('client-assets/fedi.jpg')).blob();
const image = await (await window.fetch('client-assets/fedi.jpg')).blob();
return new HttpResponse(image, {
headers: {
'Content-Type': 'image/jpeg',

View File

@@ -180,7 +180,7 @@ function applyToPreview() {
nextTick(() => {
if (currentPreviewUrl === embedPreviewUrl.value) {
// URLが変わらなくてもリロード
iframeEl.value?.contentWindow?.location.reload();
iframeEl.value?.contentWindow?.window.location.reload();
}
});
}

View File

@@ -11,54 +11,91 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- 拡張用 -->
<i v-else class="ti ti-download"></i>
</div>
<h2 :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].title }}</h2>
<div :class="$style.extInstallerNormDesc">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</div>
<MkInfo v-if="isPlugin" :warn="true">{{ i18n.ts._plugin.installWarn }}</MkInfo>
<FormSection>
<template #label>{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].metaTitle }}</template>
<div class="_gaps_s">
<FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts.name }}</template>
<template #value>{{ extension.meta.name }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ extension.meta.author }}</template>
</MkKeyValue>
</FormSplit>
<MkKeyValue v-if="isPlugin">
<template #key>{{ i18n.ts.description }}</template>
<template #value>{{ extension.meta.description ?? i18n.ts.none }}</template>
</MkKeyValue>
<MkKeyValue v-if="isPlugin">
<template #key>{{ i18n.ts.version }}</template>
<template #value>{{ extension.meta.version }}</template>
</MkKeyValue>
<MkKeyValue v-if="isPlugin">
<template #key>{{ i18n.ts.permission }}</template>
<template #value>
<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
</ul>
<template v-else>{{ i18n.ts.none }}</template>
</template>
</MkKeyValue>
<MkKeyValue v-if="isTheme">
<template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
<template #value>{{ i18n.ts[extension.meta.base ?? 'none'] }}</template>
</MkKeyValue>
<MkFolder>
<template #icon><i class="ti ti-code"></i></template>
<template #label>{{ i18n.ts._plugin.viewSource }}</template>
<MkCode :code="extension.raw"/>
</MkFolder>
</div>
</FormSection>
<h2 v-if="isPlugin" :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller._plugin.title }}</h2>
<h2 v-else-if="isTheme" :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller._theme.title }}</h2>
<MkInfo :warn="true">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</MkInfo>
<div v-if="isPlugin" class="_gaps_s">
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-info-circle"></i></template>
<template #label>{{ i18n.ts.metadata }}</template>
<div class="_gaps_s">
<FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts.name }}</template>
<template #value>{{ extension.meta.name }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ extension.meta.author }}</template>
</MkKeyValue>
</FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts.description }}</template>
<template #value>{{ extension.meta.description ?? i18n.ts.none }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.version }}</template>
<template #value>{{ extension.meta.version }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.permission }}</template>
<template #value>
<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
</ul>
<template v-else>{{ i18n.ts.none }}</template>
</template>
</MkKeyValue>
</div>
</MkFolder>
<MkFolder :withSpacer="false">
<template #icon><i class="ti ti-code"></i></template>
<template #label>{{ i18n.ts._plugin.viewSource }}</template>
<MkCode :code="extension.raw"/>
</MkFolder>
</div>
<div v-else-if="isTheme" class="_gaps_s">
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-info-circle"></i></template>
<template #label>{{ i18n.ts.metadata }}</template>
<div class="_gaps_s">
<FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts.name }}</template>
<template #value>{{ extension.meta.name }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ extension.meta.author }}</template>
</MkKeyValue>
</FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
<template #value>{{ i18n.ts[extension.meta.base ?? 'none'] }}</template>
</MkKeyValue>
</div>
</MkFolder>
<MkFolder :withSpacer="false">
<template #icon><i class="ti ti-code"></i></template>
<template #label>{{ i18n.ts._theme.code }}</template>
<MkCode :code="extension.raw"/>
</MkFolder>
</div>
<slot name="additionalInfo"/>
<div class="_buttonsCenter">
<MkButton gradate rounded @click="emits('confirm')"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
<MkButton danger rounded large @click="emits('cancel')"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
<MkButton gradate rounded large @click="emits('confirm')"><i class="ti ti-download"></i> {{ i18n.ts.install }}</MkButton>
</div>
</div>
</template>
@@ -105,6 +142,7 @@ const props = defineProps<{
const emits = defineEmits<{
(ev: 'confirm'): void;
(ev: 'cancel'): void;
}>();
</script>
@@ -112,13 +150,13 @@ const emits = defineEmits<{
.extInstallerRoot {
border-radius: var(--MI-radius);
background: var(--MI_THEME-panel);
padding: 1.5rem;
padding: 20px;
}
.extInstallerIconWrapper {
width: 48px;
height: 48px;
font-size: 24px;
font-size: 20px;
line-height: 48px;
text-align: center;
border-radius: 50%;
@@ -135,10 +173,6 @@ const emits = defineEmits<{
margin: 0;
}
.extInstallerNormDesc {
text-align: center;
}
.extInstallerKVList {
margin-top: 0;
margin-bottom: 0;

View File

@@ -69,13 +69,11 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
Math.min(navigator.hardwareConcurrency - 1, 4),
);
resolve(workers);
if (_DEV_) console.log('WebGL2 in worker is supported!');
} else {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
if (_DEV_) console.log('WebGL2 in worker is not supported...');
}
testWorker.terminate();
});

View File

@@ -3,13 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { federationInstance } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import { getChartResolver } from '../../.storybook/charts.js';
import MkInstanceCardMini from './MkInstanceCardMini.vue';
import type { StoryObj } from '@storybook/vue3';
export const Default = {
render(args) {
@@ -48,7 +47,7 @@ export const Default = {
const url = new URL(urlStr);
if (url.href.startsWith('https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/')) {
const image = await (await fetch(`client-assets/${url.pathname.split('/').pop()}`)).blob();
const image = await (await window.fetch(`client-assets/${url.pathname.split('/').pop()}`)).blob();
return new HttpResponse(image, {
headers: {
'Content-Type': 'image/jpeg',

View File

@@ -183,14 +183,14 @@ onMounted(() => {
lightbox?.pswp?.element?.focus({
preventScroll: true,
});
history.pushState(null, '', '#pswp');
window.history.pushState(null, '', '#pswp');
});
lightbox.on('destroy', () => {
focusParent(activeEl, true, false);
activeEl = null;
if (window.location.hash === '#pswp') {
history.back();
window.history.back();
}
});

View File

@@ -214,7 +214,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch } from 'vue';
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch, shallowRef } from 'vue';
import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
import type { Keymap } from '@/utility/hotkey.js';
import MkSwitchButton from '@/components/MkSwitch.button.vue';
@@ -292,7 +292,7 @@ watch(() => props.items, () => {
});
const childMenu = ref<MenuItem[] | null>();
const childTarget = useTemplateRef('childTarget');
const childTarget = shallowRef<HTMLElement>();
function closeChild() {
childMenu.value = null;

View File

@@ -0,0 +1,21 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<MkAnimBg style="position: absolute;"/>
<div class="_pageScrollable" style="position: absolute; top: 0; width: 100%; height: 100%;">
<slot></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import MkAnimBg from '@/components/MkAnimBg.vue';
</script>
<style lang="scss" module>
</style>

View File

@@ -267,7 +267,7 @@ async function onSubmit(): Promise<void> {
'testcaptcha-response': testcaptchaResponse.value,
};
const res = await fetch(`${config.apiUrl}/signup`, {
const res = await window.fetch(`${config.apiUrl}/signup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@@ -87,7 +87,7 @@ function openWindow() {
function nav(ev: MouseEvent) {
if (behavior === 'browser') {
location.href = props.to;
window.location.href = props.to;
return;
}

View File

@@ -320,7 +320,7 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
}
const res = this.navigate(fullPath);
if (res.route.path === '/:(*)') {
location.href = fullPath;
window.location.href = fullPath;
} else {
this.emit('push', {
beforeFullPath,

View File

@@ -172,7 +172,7 @@ export const navbarItemDef = reactive({
title: i18n.ts.reload,
icon: 'ti ti-refresh',
action: (ev) => {
location.reload();
window.location.reload();
},
},
profile: {

View File

@@ -64,7 +64,7 @@ function accepted() {
if (session.value && session.value.app.callbackUrl) {
const url = new URL(session.value.app.callbackUrl);
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}`;
window.location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`;
}
}

View File

@@ -632,7 +632,7 @@ function loadMonoTextures() {
src = URL.createObjectURL(monoTextures[mono.img]);
monoTextureUrls[mono.img] = src;
} else {
const res = await fetch(mono.img);
const res = await window.fetch(mono.img);
const blob = await res.blob();
monoTextures[mono.img] = blob;
src = URL.createObjectURL(blob);

View File

@@ -4,14 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="500">
<MkPageWithAnimBg>
<MkSpacer :contentMax="550" :marginMax="50">
<MkLoading v-if="uiPhase === 'fetching'"/>
<MkExtensionInstaller v-else-if="uiPhase === 'confirm' && data" :extension="data" @confirm="install()">
<MkExtensionInstaller v-else-if="uiPhase === 'confirm' && data" :extension="data" @confirm="install()" @cancel="close_()">
<template #additionalInfo>
<FormSection>
<template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template>
<div class="_gaps_s">
<MkKeyValue>
<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template>
@@ -35,12 +33,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<h2 :class="$style.extInstallerTitle">{{ errorKV?.title }}</h2>
<div :class="$style.extInstallerNormDesc">{{ errorKV?.description }}</div>
<div class="_buttonsCenter">
<MkButton @click="goBack()">{{ i18n.ts.goBack }}</MkButton>
<MkButton @click="goToMisskey()">{{ i18n.ts.goToMisskey }}</MkButton>
<MkButton @click="close_()">{{ i18n.ts.close }}</MkButton>
</div>
</div>
</MkSpacer>
</MkStickyContainer>
</MkPageWithAnimBg>
</template>
<script lang="ts" setup>
@@ -60,6 +57,7 @@ import { parseThemeCode, installTheme } from '@/theme.js';
import { unisonReload } from '@/utility/unison-reload.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import MkPageWithAnimBg from '@/components/MkPageWithAnimBg.vue';
const uiPhase = ref<'fetching' | 'confirm' | 'error'>('fetching');
const errorKV = ref<{
@@ -75,12 +73,12 @@ const hash = ref<string | null>(null);
const data = ref<Extension | null>(null);
function goBack(): void {
history.back();
}
function goToMisskey(): void {
location.href = '/';
function close_(): void {
if (window.history.length === 1) {
window.close();
} else {
window.history.back();
}
}
async function fetch() {
@@ -207,9 +205,9 @@ async function install() {
try {
await installPlugin(data.value.raw, data.value.meta as AiScriptPluginMeta);
os.success();
nextTick(() => {
unisonReload('/');
});
window.setTimeout(() => {
close_();
}, 3000);
} catch (err) {
errorKV.value = {
title: i18n.ts._externalResourceInstaller._errors._pluginInstallFailed.title,
@@ -223,9 +221,9 @@ async function install() {
if (!data.value.meta) return;
await installTheme(data.value.raw);
os.success();
nextTick(() => {
location.href = '/settings/theme';
});
window.setTimeout(() => {
close_();
}, 3000);
}
}
@@ -234,10 +232,6 @@ url.value = urlParams.get('url');
hash.value = urlParams.get('hash');
fetch();
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
title: i18n.ts._externalResourceInstaller.title,
icon: 'ti ti-download',

View File

@@ -31,7 +31,7 @@ import MkButton from '@/components/MkButton.vue';
const state = ref<'fetching' | 'done'>('fetching');
function fetch() {
const params = new URL(location.href).searchParams;
const params = new URL(window.location.href).searchParams;
// acctのほうはdeprecated
let uri = params.get('uri') ?? params.get('acct');
@@ -76,12 +76,12 @@ function close(): void {
// 閉じなければ100ms後タイムラインに
window.setTimeout(() => {
location.href = '/';
window.location.href = '/';
}, 100);
}
function goToMisskey(): void {
location.href = '/';
window.location.href = '/';
}
fetch();

View File

@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<MkAnimBg style="position: fixed; top: 0;"/>
<MkPageWithAnimBg>
<div :class="$style.formContainer">
<div :class="$style.form">
<MkAuthConfirm
@@ -25,16 +24,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkAuthConfirm>
</div>
</div>
</div>
</MkPageWithAnimBg>
</template>
<script lang="ts" setup>
import { computed, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkAnimBg from '@/components/MkAnimBg.vue';
import MkPageWithAnimBg from '@/components/MkPageWithAnimBg.vue';
import MkAuthConfirm from '@/components/MkAuthConfirm.vue';
import { i18n } from '@/i18n.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
@@ -64,7 +61,7 @@ async function onAccept(token: string) {
const cbUrl = new URL(props.callback);
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();
window.location.href = cbUrl.toString();
} else {
authRoot.value?.showUI('success');
}

View File

@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<MkAnimBg style="position: fixed; top: 0;"/>
<MkPageWithAnimBg>
<div :class="$style.formContainer">
<div :class="$style.form">
<MkAuthConfirm
@@ -19,12 +18,12 @@ SPDX-License-Identifier: AGPL-3.0-only
/>
</div>
</div>
</div>
</MkPageWithAnimBg>
</template>
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import MkAnimBg from '@/components/MkAnimBg.vue';
import MkPageWithAnimBg from '@/components/MkPageWithAnimBg.vue';
import { definePage } from '@/page.js';
import MkAuthConfirm from '@/components/MkAuthConfirm.vue';

View File

@@ -182,12 +182,12 @@ function close(): void {
// 閉じなければ100ms後タイムラインに
window.setTimeout(() => {
location.href = '/';
window.location.href = '/';
}, 100);
}
function goToMisskey(): void {
location.href = '/';
window.location.href = '/';
}
function onPosted(): void {

View File

@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<MkAnimBg style="position: fixed; top: 0;"/>
<MkPageWithAnimBg>
<div :class="$style.formContainer">
<form :class="$style.form" class="_panel" @submit.prevent="submit()">
<div :class="$style.banner">
@@ -21,13 +20,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</form>
</div>
</div>
</MkPageWithAnimBg>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkAnimBg from '@/components/MkAnimBg.vue';
import MkPageWithAnimBg from '@/components/MkPageWithAnimBg.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
@@ -64,8 +63,8 @@ function submit() {
min-height: 100svh;
padding: 32px 32px 64px 32px;
box-sizing: border-box;
display: grid;
place-content: center;
display: grid;
place-content: center;
}
.form {

View File

@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<MkAnimBg style="position: fixed; top: 0;"/>
<MkPageWithAnimBg>
<div :class="$style.formContainer">
<form :class="$style.form" class="_panel" @submit.prevent="submit()">
<div :class="$style.title">
@@ -35,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</form>
</div>
</div>
</MkPageWithAnimBg>
</template>
<script lang="ts" setup>
@@ -46,7 +45,7 @@ import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import MkAnimBg from '@/components/MkAnimBg.vue';
import MkPageWithAnimBg from '@/components/MkPageWithAnimBg.vue';
import { login } from '@/accounts.js';
const username = ref('');

View File

@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { v4 as uuid } from 'uuid';
import type { PreferencesProfile, StorageProvider } from '@/preferences/manager.js';
import { cloudBackup } from '@/preferences/utility.js';
import { miLocalStorage } from '@/local-storage.js';
@@ -11,8 +10,7 @@ import { isSameScope, PreferencesManager } from '@/preferences/manager.js';
import { store } from '@/store.js';
import { $i } from '@/i.js';
import { misskeyApi } from '@/utility/misskey-api.js';
const TAB_ID = uuid();
import { TAB_ID } from '@/tab-id.js';
function createPrefManager(storageProvider: StorageProvider) {
let profile: PreferencesProfile;

View File

@@ -32,6 +32,8 @@ export type SoundStore = {
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
export const PREF_DEF = {
// TODO: 持つのはホストやユーザーID、ユーザー名など最低限にしといて、その他のプロフィール情報はpreferences外で管理した方が綺麗そう
// 現状だと、updateCurrentAccount/updateCurrentAccountPartialが呼ばれるたびに「設定」へのcommitが行われて不自然(明らかに設定の更新とは捉えにくい)だし
accounts: {
default: [] as [host: string, user: Misskey.entities.User][],
},

View File

@@ -139,9 +139,16 @@ export class PreferencesManager {
}
public commit<K extends keyof PREF>(key: K, value: ValueOf<K>) {
console.log('prefer:commit', key, value);
const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除
this.rewriteRawState(key, value);
if (deepEqual(this.s[key], v)) {
console.log('(skip) prefer:commit', key, v);
return;
}
console.log('prefer:commit', key, v);
this.rewriteRawState(key, v);
const record = this.getMatchedRecordOf(key);
@@ -149,7 +156,7 @@ export class PreferencesManager {
this.profile.preferences[key].push([makeScope({
server: host,
account: $i!.id,
}), value, {}]);
}), v, {}]);
this.save();
return;
}
@@ -157,12 +164,12 @@ export class PreferencesManager {
if (parseScope(record[0]).server == null && this.isServerDependentKey(key)) {
this.profile.preferences[key].push([makeScope({
server: host,
}), value, {}]);
}), v, {}]);
this.save();
return;
}
record[1] = value;
record[1] = v;
this.save();
if (record[2].sync) {

View File

@@ -17,10 +17,10 @@ export function createRouter(fullPath: string): Router {
return new Nirax(ROUTE_DEF, fullPath, !!$i, page(() => import('@/pages/not-found.vue')));
}
export const mainRouter = createRouter(location.pathname + location.search + location.hash);
export const mainRouter = createRouter(window.location.pathname + window.location.search + window.location.hash);
window.addEventListener('popstate', (event) => {
mainRouter.replace(location.pathname + location.search + location.hash);
mainRouter.replace(window.location.pathname + window.location.search + window.location.hash);
});
mainRouter.addListener('push', ctx => {

View File

@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { v4 as uuid } from 'uuid';
// HMR有効時にバグか知らんけど複数回実行されるのでその対策
export const TAB_ID = window.sessionStorage.getItem('TAB_ID') ?? uuid();
window.sessionStorage.setItem('TAB_ID', TAB_ID);
console.log('TAB_ID', TAB_ID);

View File

@@ -34,7 +34,7 @@ function resetDisconnected() {
}
function reload() {
location.reload();
window.location.reload();
}
useStream().on('_disconnected_', onDisconnected);

View File

@@ -139,7 +139,7 @@ if (window.innerWidth < 1024) {
const currentUI = miLocalStorage.getItem('ui');
miLocalStorage.setItem('ui_temp', currentUI ?? 'default');
miLocalStorage.setItem('ui', 'default');
location.reload();
window.location.reload();
}
document.documentElement.style.overflowY = 'scroll';

View File

@@ -5,9 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
<div style="height: 100%;">
<RouterView/>
</div>
<RouterView/>
<XCommon/>
</div>
@@ -39,12 +37,11 @@ provideMetadataReceiver((metadataGetter) => {
}
});
provideReactiveMetadata(pageMetadata);
document.documentElement.style.overflowY = 'scroll';
</script>
<style lang="scss" module>
.root {
min-height: 100dvh;
position: relative;
height: 100dvh;
}
</style>

View File

@@ -170,7 +170,7 @@ if (window.innerWidth > 1024) {
if (tempUI) {
miLocalStorage.setItem('ui', tempUI);
miLocalStorage.removeItem('ui_temp');
location.reload();
window.location.reload();
}
}

View File

@@ -37,7 +37,7 @@ const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
const pageMetadata = ref<null | PageMetadata>(null);
const showBottom = !(new URLSearchParams(location.search)).has('zen') && ui === 'deck';
const showBottom = !(new URLSearchParams(window.location.search)).has('zen') && ui === 'deck';
provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => {

View File

@@ -35,6 +35,6 @@ export async function reloadAsk(opts: {
if (opts.unison) {
unisonReload();
} else {
location.reload();
window.location.reload();
}
}

View File

@@ -108,7 +108,7 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; })
let response: Response;
try {
response = await fetch(url);
response = await window.fetch(url);
} catch (err) {
return;
}

View File

@@ -12,9 +12,9 @@ export const reloadChannel = new BroadcastChannel<string | null>('reload');
export function unisonReload(path?: string) {
if (path !== undefined) {
reloadChannel.postMessage(path);
location.href = path;
window.location.href = path;
} else {
reloadChannel.postMessage(null);
location.reload();
window.location.reload();
}
}