Merge upstream
This commit is contained in:
commit
bc9acabd6c
65 changed files with 502 additions and 500 deletions
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous">
|
||||
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@3.5.0/dist/tabler-icons.min.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@latest/dist/tabler-icons.min.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
|
||||
<style>
|
||||
html {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"watch": "vite",
|
||||
"dev": "vite --config vite.config.local-dev.ts --debug hmr",
|
||||
"build": "vite build",
|
||||
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
|
||||
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
|
||||
|
@ -27,7 +26,7 @@
|
|||
"@rollup/plugin-typescript": "12.1.2",
|
||||
"@rollup/pluginutils": "5.1.4",
|
||||
"@syuilo/aiscript": "0.19.0",
|
||||
"@tabler/icons-webfont": "3.28.1",
|
||||
"@tabler/icons-webfont": "3.29.0",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
|
@ -41,7 +40,7 @@
|
|||
"chartjs-chart-matrix": "2.0.1",
|
||||
"chartjs-plugin-gradient": "0.6.1",
|
||||
"chartjs-plugin-zoom": "2.2.0",
|
||||
"chromatic": "11.24.0",
|
||||
"chromatic": "11.25.2",
|
||||
"compare-versions": "6.1.1",
|
||||
"cropperjs": "2.0.0-rc.0",
|
||||
"date-fns": "4.1.0",
|
||||
|
@ -59,13 +58,13 @@
|
|||
"misskey-reversi": "workspace:*",
|
||||
"photoswipe": "5.4.4",
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.30.1",
|
||||
"rollup": "4.34.0",
|
||||
"sanitize-html": "2.14.0",
|
||||
"sass": "1.83.4",
|
||||
"shiki": "1.27.2",
|
||||
"shiki": "2.2.0",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.172.0",
|
||||
"three": "0.173.0",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tsc-alias": "1.8.10",
|
||||
|
@ -73,7 +72,7 @@
|
|||
"typescript": "5.7.3",
|
||||
"uuid": "11.0.5",
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "6.0.7",
|
||||
"vite": "6.0.11",
|
||||
"vue": "3.5.13",
|
||||
"vue-gtag": "2.0.1",
|
||||
"vuedraggable": "next",
|
||||
|
@ -82,49 +81,49 @@
|
|||
"devDependencies": {
|
||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||
"@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3",
|
||||
"@storybook/addon-actions": "8.5.0",
|
||||
"@storybook/addon-essentials": "8.5.0",
|
||||
"@storybook/addon-interactions": "8.5.0",
|
||||
"@storybook/addon-links": "8.5.0",
|
||||
"@storybook/addon-mdx-gfm": "8.5.0",
|
||||
"@storybook/addon-storysource": "8.5.0",
|
||||
"@storybook/blocks": "8.5.0",
|
||||
"@storybook/components": "8.5.0",
|
||||
"@storybook/core-events": "8.5.0",
|
||||
"@storybook/manager-api": "8.5.0",
|
||||
"@storybook/preview-api": "8.5.0",
|
||||
"@storybook/react": "8.5.0",
|
||||
"@storybook/react-vite": "8.5.0",
|
||||
"@storybook/test": "8.5.0",
|
||||
"@storybook/theming": "8.5.0",
|
||||
"@storybook/types": "8.5.0",
|
||||
"@storybook/vue3": "8.5.0",
|
||||
"@storybook/vue3-vite": "8.5.0",
|
||||
"@storybook/addon-actions": "8.5.2",
|
||||
"@storybook/addon-essentials": "8.5.2",
|
||||
"@storybook/addon-interactions": "8.5.2",
|
||||
"@storybook/addon-links": "8.5.2",
|
||||
"@storybook/addon-mdx-gfm": "8.5.2",
|
||||
"@storybook/addon-storysource": "8.5.2",
|
||||
"@storybook/blocks": "8.5.2",
|
||||
"@storybook/components": "8.5.2",
|
||||
"@storybook/core-events": "8.5.2",
|
||||
"@storybook/manager-api": "8.5.2",
|
||||
"@storybook/preview-api": "8.5.2",
|
||||
"@storybook/react": "8.5.2",
|
||||
"@storybook/react-vite": "8.5.2",
|
||||
"@storybook/test": "8.5.2",
|
||||
"@storybook/theming": "8.5.2",
|
||||
"@storybook/types": "8.5.2",
|
||||
"@storybook/vue3": "8.5.2",
|
||||
"@storybook/vue3-vite": "8.5.2",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/canvas-confetti": "^1.6.4",
|
||||
"@types/escape-regexp": "0.0.3",
|
||||
"@types/estree": "1.0.6",
|
||||
"@types/matter-js": "0.19.8",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "22.10.7",
|
||||
"@types/node": "22.13.0",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.13.0",
|
||||
"@types/three": "0.172.0",
|
||||
"@types/three": "0.173.0",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.5.13",
|
||||
"@types/ws": "8.5.14",
|
||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||
"@typescript-eslint/parser": "7.10.0",
|
||||
"@vitest/coverage-v8": "2.1.8",
|
||||
"@vitest/coverage-v8": "3.0.4",
|
||||
"@vue/runtime-core": "3.5.13",
|
||||
"acorn": "8.14.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.17.0",
|
||||
"cypress": "14.0.1",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-vue": "9.32.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"happy-dom": "16.6.0",
|
||||
"happy-dom": "16.8.1",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"msw": "2.7.0",
|
||||
|
@ -134,10 +133,10 @@
|
|||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"start-server-and-test": "2.0.10",
|
||||
"storybook": "8.5.0",
|
||||
"storybook": "8.5.2",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "2.1.8",
|
||||
"vitest": "3.0.4",
|
||||
"vitest-fetch-mock": "0.3.0",
|
||||
"vue-component-type-helpers": "2.2.0",
|
||||
"vue-eslint-parser": "9.4.3",
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// devモードで起動される際(index.htmlを使うとき)はrouterが暴発してしまってうまく読み込めない。
|
||||
// よって、devモードとして起動されるときはビルド時に組み込む形としておく。
|
||||
// (pnpm start時はpugファイルの中で静的リソースとして読み込むようになっており、この問題は起こっていない)
|
||||
import '@tabler/icons-webfont/dist/tabler-icons.scss';
|
||||
|
||||
await main();
|
||||
|
||||
import('@/_boot_.js');
|
||||
|
||||
/**
|
||||
* backend/src/server/web/boot.jsで差し込まれている起動処理のうち、最低限必要なものを模倣するための処理
|
||||
*/
|
||||
async function main() {
|
||||
const forceError = localStorage.getItem('forceError');
|
||||
if (forceError != null) {
|
||||
renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
|
||||
}
|
||||
|
||||
const metaRes = await window.fetch('/api/meta', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({}),
|
||||
credentials: 'omit',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (metaRes.status !== 200) {
|
||||
renderError('META_FETCH');
|
||||
return;
|
||||
}
|
||||
const meta = await metaRes.json();
|
||||
//#region Detect language & fetch translations
|
||||
|
||||
// dev-modeの場合は常に取り直す
|
||||
const supportedLangs = _LANGS_.map(it => it[0]);
|
||||
let lang: string | null | undefined = localStorage.getItem('lang');
|
||||
if (lang == null || !supportedLangs.includes(lang)) {
|
||||
if (supportedLangs.includes(navigator.language)) {
|
||||
lang = navigator.language;
|
||||
} else {
|
||||
lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
|
||||
|
||||
// Fallback
|
||||
if (lang == null) lang = 'ko-KR';
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:今のままだと言語ファイル変更後はpnpm devをリスタートする必要があるので、chokidarを使ったり等で対応できるようにする
|
||||
const locale = _LANGS_FULL_.find(it => it[0] === lang);
|
||||
localStorage.setItem('lang', lang);
|
||||
localStorage.setItem('locale', JSON.stringify(locale[1]));
|
||||
localStorage.setItem('localeVersion', _VERSION_);
|
||||
//#endregion
|
||||
|
||||
//#region Theme
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
for (const [k, v] of Object.entries(JSON.parse(theme))) {
|
||||
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
||||
|
||||
// HTMLの theme-color 適用
|
||||
if (k === 'htmlThemeColor') {
|
||||
for (const tag of document.head.children) {
|
||||
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
||||
tag.setAttribute('content', v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const colorScheme = localStorage.getItem('colorScheme');
|
||||
if (colorScheme) {
|
||||
document.documentElement.style.setProperty('color-scheme', colorScheme);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const fontSize = localStorage.getItem('fontSize');
|
||||
if (fontSize) {
|
||||
document.documentElement.classList.add('f-' + fontSize);
|
||||
}
|
||||
|
||||
const useSystemFont = localStorage.getItem('useSystemFont');
|
||||
if (useSystemFont) {
|
||||
document.documentElement.classList.add('useSystemFont');
|
||||
}
|
||||
|
||||
const wallpaper = localStorage.getItem('wallpaper') ?? meta.backgroundImageUrl;
|
||||
if (wallpaper) {
|
||||
document.documentElement.style.background = `url(${wallpaper}) no-repeat fixed center`;
|
||||
document.documentElement.style.backgroundSize = 'cover';
|
||||
}
|
||||
|
||||
const customCss = localStorage.getItem('customCss');
|
||||
if (customCss && customCss.length > 0) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = customCss;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
function renderError(code: string, details?: string) {
|
||||
console.log(code, details);
|
||||
}
|
|
@ -43,6 +43,12 @@ export async function signout() {
|
|||
if (!$i) return;
|
||||
|
||||
waiting();
|
||||
document.cookie.split(';').forEach((cookie) => {
|
||||
const cookieName = cookie.split('=')[0].trim();
|
||||
if (cookieName === 'token') {
|
||||
document.cookie = `${cookieName}=; max-age=0; path=/`;
|
||||
}
|
||||
});
|
||||
miLocalStorage.removeItem('account');
|
||||
await removeAccount($i.id);
|
||||
const accounts = await getAccounts();
|
||||
|
|
|
@ -100,6 +100,11 @@ export async function common(createVue: () => App<Element>) {
|
|||
// タッチデバイスでCSSの:hoverを機能させる
|
||||
document.addEventListener('touchend', () => {}, { passive: true });
|
||||
|
||||
// URLに#pswpを含む場合は取り除く
|
||||
if (location.hash === '#pswp') {
|
||||
history.replaceState(null, '', location.href.replace('#pswp', ''));
|
||||
}
|
||||
|
||||
// 一斉リロード
|
||||
reloadChannel.addEventListener('message', path => {
|
||||
if (path !== null) location.href = path;
|
||||
|
|
|
@ -26,22 +26,34 @@ let prevTime = 0;
|
|||
let angle1 = 0;
|
||||
let angle2 = 0;
|
||||
|
||||
let scene, camera, renderer, width, height, uniforms, texture, maskTexture, dataArray1, dataArray2, dataArrayOrigin, bufferLength: number;
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.OrthographicCamera();
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
|
||||
let width: number;
|
||||
let height: number;
|
||||
let uniforms: { [p: string]: THREE.IUniform };
|
||||
let texture: THREE.Texture;
|
||||
let maskTexture: THREE.Texture;
|
||||
let dataArray1: Uint8Array;
|
||||
let dataArray2: Uint8Array;
|
||||
let dataArrayOrigin: Uint8Array;
|
||||
let bufferLength: number;
|
||||
|
||||
const init = () => {
|
||||
const parent = container.value ?? { offsetWidth: 0 };
|
||||
width = parent.offsetWidth;
|
||||
height = Math.floor(width * 9 / 16);
|
||||
|
||||
scene = new THREE.Scene();
|
||||
camera = new THREE.OrthographicCamera();
|
||||
scene.clear();
|
||||
camera.clear();
|
||||
|
||||
camera.left = width / -2;
|
||||
camera.right = width / 2;
|
||||
camera.top = height / 2;
|
||||
camera.bottom = height / -2;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(width, height);
|
||||
|
||||
if (container.value) {
|
||||
|
@ -176,7 +188,7 @@ const animate = (time) => {
|
|||
renderer.render(scene, camera);
|
||||
};
|
||||
|
||||
const onResize = () => {
|
||||
const resize = () => {
|
||||
const parent = container.value ?? { offsetWidth: 0 };
|
||||
width = parent.offsetWidth;
|
||||
height = Math.floor(width * 9 / 16);
|
||||
|
@ -189,17 +201,25 @@ const onResize = () => {
|
|||
uniforms.resolution.value.set(width, height);
|
||||
};
|
||||
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
resize();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
nextTick().then(() => {
|
||||
init();
|
||||
window.addEventListener('resize', onResize);
|
||||
resize();
|
||||
});
|
||||
|
||||
if (!container.value) return;
|
||||
ro.observe(container.value);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (renderer) {
|
||||
renderer.dispose();
|
||||
}
|
||||
ro.disconnect();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
|
|
|
@ -48,8 +48,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</audio>
|
||||
<div :class="[$style.controlsChild, $style.controlsLeft]">
|
||||
<button class="_button" :class="$style.controlButton" @click.prevent.stop="togglePlayPause">
|
||||
<i v-if="isPlaying" class="ti ti-player-pause-filled"></i>
|
||||
<i v-else class="ti ti-player-play-filled"></i>
|
||||
<i v-if="isPlaying" class="ti-filled ti-filled-player-pause"></i>
|
||||
<i v-else class="ti-filled ti-filled-player-play"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div :class="[$style.controlsChild, $style.controlsRight]">
|
||||
|
|
|
@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<source :src="video.url">
|
||||
</video>
|
||||
<button v-if="isReady && !isPlaying" class="_button" :class="$style.videoOverlayPlayButton" @click="togglePlayPause"><i class="ti ti-player-play-filled"></i></button>
|
||||
<button v-if="isReady && !isPlaying" class="_button" :class="$style.videoOverlayPlayButton" @click="togglePlayPause"><i class="ti-filled ti-filled-player-play"></i></button>
|
||||
<div v-else-if="!isActuallyPlaying" :class="$style.videoLoading">
|
||||
<MkLoading/>
|
||||
</div>
|
||||
|
@ -75,8 +75,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div v-if="videoControls" :class="$style.videoControls" @click.self="togglePlayPause">
|
||||
<div :class="[$style.controlsChild, $style.controlsLeft]">
|
||||
<button class="_button" :class="$style.controlButton" @click.prevent.stop="togglePlayPause">
|
||||
<i v-if="isPlaying" class="ti ti-player-pause-filled"></i>
|
||||
<i v-else class="ti ti-player-play-filled"></i>
|
||||
<i v-if="isPlaying" class="ti-filled ti-filled-player-pause"></i>
|
||||
<i v-else class="ti-filled ti-filled-player-play"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div :class="[$style.controlsChild, $style.controlsRight]">
|
||||
|
|
|
@ -119,7 +119,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<i class="ti ti-ban"></i>
|
||||
</button>
|
||||
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
|
||||
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
|
||||
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti-filled ti-filled-heart" style="color: var(--eventReactionHeart);"></i>
|
||||
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || $i && !$i.policies.canUseReaction" class="ti ti-heart"></i>
|
||||
<i v-else class="ti ti-plus"></i>
|
||||
|
@ -594,7 +594,7 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||
contain: content;
|
||||
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: auto none auto 128px;
|
||||
contain-intrinsic-size: none auto 128px;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
|
|
|
@ -127,7 +127,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<i class="ti ti-ban"></i>
|
||||
</button>
|
||||
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
|
||||
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
|
||||
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti-filled ti-filled-heart" style="color: var(--eventReactionHeart);"></i>
|
||||
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || $i && !$i.policies.canUseReaction" class="ti ti-heart"></i>
|
||||
<i v-else class="ti ti-plus"></i>
|
||||
|
|
|
@ -220,7 +220,7 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
|
|||
contain: content;
|
||||
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: auto none auto 100px;
|
||||
contain-intrinsic-size: none auto 100px;
|
||||
}
|
||||
|
||||
.head {
|
||||
|
|
|
@ -24,7 +24,7 @@ import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
|||
import { useStream } from '@/stream.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { deepMerge } from '@/scripts/merge.js';
|
||||
import { $i, iAmModerator } from '@/account.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { Paging } from '@/components/MkPagination.vue';
|
||||
|
@ -108,7 +108,6 @@ async function prepend(data) {
|
|||
let connection: Misskey.ChannelConnection | null = null;
|
||||
let connection2: Misskey.ChannelConnection | null = null;
|
||||
let paginationQuery: Paging | null = null;
|
||||
const minimize = !iAmModerator;
|
||||
|
||||
const stream = useStream();
|
||||
|
||||
|
@ -117,13 +116,13 @@ function connectChannel() {
|
|||
if (props.antenna == null) return;
|
||||
connection = stream.useChannel('antenna', {
|
||||
antennaId: props.antenna,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
} else if (props.src === 'home') {
|
||||
connection = stream.useChannel('homeTimeline', {
|
||||
withRenotes: props.withRenotes,
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
connection2 = stream.useChannel('main');
|
||||
} else if (props.src === 'local') {
|
||||
|
@ -131,27 +130,27 @@ function connectChannel() {
|
|||
withRenotes: props.withRenotes,
|
||||
withReplies: props.withReplies,
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
} else if (props.src === 'media') {
|
||||
connection = stream.useChannel('hybridTimeline', {
|
||||
withRenotes: props.withRenotes,
|
||||
withReplies: props.withReplies,
|
||||
withFiles: true,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
} else if (props.src === 'social') {
|
||||
connection = stream.useChannel('hybridTimeline', {
|
||||
withRenotes: props.withRenotes,
|
||||
withReplies: props.withReplies,
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
} else if (props.src === 'global') {
|
||||
connection = stream.useChannel('globalTimeline', {
|
||||
withRenotes: props.withRenotes,
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
} else if (props.src === 'mentions') {
|
||||
connection = stream.useChannel('main');
|
||||
|
@ -170,19 +169,19 @@ function connectChannel() {
|
|||
withRenotes: props.withRenotes,
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
listId: props.list,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
} else if (props.src === 'channel') {
|
||||
if (props.channel == null) return;
|
||||
connection = stream.useChannel('channel', {
|
||||
channelId: props.channel,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
} else if (props.src === 'role') {
|
||||
if (props.role == null) return;
|
||||
connection = stream.useChannel('roleTimeline', {
|
||||
roleId: props.role,
|
||||
minimize: minimize,
|
||||
minimize: true,
|
||||
});
|
||||
}
|
||||
if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<!--
|
||||
開発モードのviteはこのファイルを起点にサーバーを起動します。
|
||||
このファイルに書かれた [t]js のリンクと (s)cssのリンクと、その依存関係にあるファイルはビルドされます
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>[DEV] Loading...</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy-Report-Only"
|
||||
content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/ https://fonts.gstatic.com/ https://www.google-analytics.com/ https://www.googletagmanager.com/;
|
||||
worker-src 'self';
|
||||
script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://www.googletagmanager.com https://esm.sh;
|
||||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.gstatic.com https://www.googletagmanager.com;
|
||||
img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://fonts.gstatic.com https://www.googletagmanager.com;
|
||||
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
||||
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://api.pwnedpasswords.com https://www.google-analytics.com https://analytics.google.com;
|
||||
frame-src *;"
|
||||
/>
|
||||
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="misskey_app"></div>
|
||||
<script type="module" src="./_dev_boot_.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -18,6 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div>
|
||||
<div v-for="ad in ads" class="_panel _gaps_m" :class="$style.ad">
|
||||
<MkAd v-if="ad.url" :key="ad.id" :specify="ad"/>
|
||||
<MkInput v-if="ad.id" v-model="ad.id" :readonly="true">
|
||||
<template #label>ID</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="ad.url" type="url">
|
||||
<template #label>URL</template>
|
||||
</MkInput>
|
||||
|
|
|
@ -2,13 +2,29 @@
|
|||
<MkStickyContainer>
|
||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="900">
|
||||
<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
|
||||
<MkInput v-model="movedFromId" style="margin: 0; flex: 1;">
|
||||
<template #label> {{ i18n.ts.moveFromId }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="movedToId" style="margin: 0; flex: 1;">
|
||||
<template #label> {{ i18n.ts.movedToId }}</template>
|
||||
</MkInput>
|
||||
<div style="display: flex; flex-direction: column; gap: var(--margin); flex-wrap: wrap;">
|
||||
<div :class="$style.inputs">
|
||||
<MkSelect v-model="from" :class="$style.input">
|
||||
<template #label>{{ i18n.ts._accountMigration.movedFromServer }}</template>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
||||
<option value="local">{{ i18n.ts.local }}</option>
|
||||
</MkSelect>
|
||||
<MkSelect v-model="to" :class="$style.input">
|
||||
<template #label>{{ i18n.ts._accountMigration.movedToServer }}</template>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
||||
<option value="local">{{ i18n.ts.local }}</option>
|
||||
</MkSelect>
|
||||
</div>
|
||||
<div :class="$style.inputs">
|
||||
<MkInput v-model="movedFromId" :class="$style.input">
|
||||
<template #label> {{ i18n.ts.moveFromId }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="movedToId" :class="$style.input">
|
||||
<template #label> {{ i18n.ts.movedToId }}</template>
|
||||
</MkInput>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);">
|
||||
|
@ -48,11 +64,15 @@ import { i18n } from '@/i18n.js';
|
|||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
|
||||
const logs = shallowRef<InstanceType<typeof MkPagination>>();
|
||||
|
||||
const movedToId = ref('');
|
||||
const movedFromId = ref('');
|
||||
const from = ref('all');
|
||||
const to = ref('all');
|
||||
|
||||
const pagination = {
|
||||
endpoint: 'admin/show-user-account-move-logs' as const,
|
||||
|
@ -60,6 +80,8 @@ const pagination = {
|
|||
params: computed(() => ({
|
||||
movedFromId: movedFromId.value === '' ? null : movedFromId.value,
|
||||
movedToId: movedToId.value === '' ? null : movedToId.value,
|
||||
from: from.value,
|
||||
to: to.value,
|
||||
})),
|
||||
};
|
||||
|
||||
|
@ -95,4 +117,14 @@ definePageMetadata(() => ({
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -182,24 +182,24 @@ definePageMetadata(() => ({
|
|||
}
|
||||
|
||||
.rkxwuolj {
|
||||
> .files {
|
||||
> .file {
|
||||
> img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
& + .file {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .body {
|
||||
padding: 32px;
|
||||
|
||||
> .files {
|
||||
> .file {
|
||||
> img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
& + .file {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .title {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
|
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div v-if="game.map == null"><i class="ti ti-dice"></i></div>
|
||||
<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
|
||||
<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
|
||||
<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i>
|
||||
<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti-filled ti-filled-circle' : 'ti ti-circle'"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -145,7 +145,6 @@ import { i18n } from '@/i18n.js';
|
|||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
import { signinRequired } from '@/account.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
@ -276,7 +275,7 @@ async function toggleBlockItem(item) {
|
|||
}
|
||||
|
||||
async function saveMutedWords(mutedWords: (string | string[])[]) {
|
||||
await misskeyApi('i/update', { mutedWords });
|
||||
await os.apiWithDialog('i/update', { mutedWords });
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { query } from '@/scripts/url.js';
|
||||
import { appendQuery, omitHttps, query } from '@/scripts/url.js';
|
||||
import { url } from '@/config.js';
|
||||
import { instance } from '@/instance.js';
|
||||
|
||||
|
@ -12,18 +12,26 @@ export function getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji'
|
|||
|
||||
if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
|
||||
// もう既にproxyっぽそうだったらurlを取り出す
|
||||
imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl;
|
||||
const url = (new URL(imageUrl)).searchParams.get('url');
|
||||
if (url) {
|
||||
imageUrl = url;
|
||||
} else if (imageUrl.startsWith(instance.mediaProxy + '/')) {
|
||||
imageUrl = imageUrl.slice(instance.mediaProxy.length + 1);
|
||||
} else if (imageUrl.startsWith('/proxy/')) {
|
||||
imageUrl = imageUrl.slice('/proxy/'.length);
|
||||
} else if (imageUrl.startsWith(localProxy + '/')) {
|
||||
imageUrl = imageUrl.slice(localProxy.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return `${mustOrigin ? localProxy : instance.mediaProxy}/${
|
||||
type === 'preview' ? 'preview.webp'
|
||||
: 'image.webp'
|
||||
}?${query({
|
||||
url: imageUrl,
|
||||
...(!noFallback ? { 'fallback': '1' } : {}),
|
||||
...(type ? { [type]: '1' } : {}),
|
||||
...(mustOrigin ? { origin: '1' } : {}),
|
||||
})}`;
|
||||
return appendQuery(
|
||||
`${mustOrigin ? localProxy : instance.mediaProxy}/${type === 'preview' ? 'preview' : 'image'}/${encodeURIComponent(omitHttps(imageUrl))}`,
|
||||
query({
|
||||
...(!noFallback ? { 'fallback': '1' } : {}),
|
||||
...(type ? { [type]: '1' } : {}),
|
||||
...(mustOrigin ? { origin: '1' } : {}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null {
|
||||
|
@ -46,8 +54,8 @@ export function getStaticImageUrl(baseUrl: string): string {
|
|||
return u.href;
|
||||
}
|
||||
|
||||
return `${instance.mediaProxy}/static.webp?${query({
|
||||
url: u.href,
|
||||
static: '1',
|
||||
})}`;
|
||||
return appendQuery(
|
||||
`${instance.mediaProxy}/static/${encodeURIComponent(omitHttps(u.href))}`,
|
||||
query({ static: '1' }),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
* 2. プロパティがundefinedの時はクエリを付けない
|
||||
* (new URLSearchParams(obj)ではそこまで丁寧なことをしてくれない)
|
||||
*/
|
||||
export function query(obj: Record<string, any>): string {
|
||||
export function query(obj: Record<string, unknown>): string {
|
||||
const params = Object.entries(obj)
|
||||
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
|
||||
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
|
||||
|
@ -21,3 +21,9 @@ export function query(obj: Record<string, any>): string {
|
|||
export function appendQuery(url: string, query: string): string {
|
||||
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
||||
}
|
||||
|
||||
export function omitHttps(url: string): string {
|
||||
if (url.startsWith('https://')) return url.slice(8);
|
||||
if (url.startsWith('https%3A%2F%2F')) return url.slice(14);
|
||||
return url;
|
||||
}
|
||||
|
|
|
@ -177,6 +177,16 @@ rt {
|
|||
}
|
||||
}
|
||||
|
||||
.ti-filled {
|
||||
width: 1.28em;
|
||||
vertical-align: -12%;
|
||||
line-height: 1em;
|
||||
|
||||
&:before {
|
||||
font-size: 128%;
|
||||
}
|
||||
}
|
||||
|
||||
.ti-fw {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
import dns from 'dns';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { defineConfig } from 'vite';
|
||||
import * as yaml from 'js-yaml';
|
||||
import locales from '../../locales/index.js';
|
||||
import { getConfig } from './vite.config.js';
|
||||
|
||||
dns.setDefaultResultOrder('ipv4first');
|
||||
|
||||
const defaultConfig = getConfig();
|
||||
|
||||
const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8'));
|
||||
|
||||
const httpUrl = `http://localhost:${port}/`;
|
||||
const websocketUrl = `ws://localhost:${port}/`;
|
||||
|
||||
const devConfig = {
|
||||
// 基本の設定は vite.config.js から引き継ぐ
|
||||
...defaultConfig,
|
||||
root: 'src',
|
||||
publicDir: '../assets',
|
||||
base: './',
|
||||
server: {
|
||||
host: 'localhost',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
changeOrigin: true,
|
||||
target: httpUrl,
|
||||
},
|
||||
'/assets': httpUrl,
|
||||
'/static-assets': httpUrl,
|
||||
'/client-assets': httpUrl,
|
||||
'/files': httpUrl,
|
||||
'/twemoji': httpUrl,
|
||||
'/fluent-emoji': httpUrl,
|
||||
'/sw.js': httpUrl,
|
||||
'/streaming': {
|
||||
target: websocketUrl,
|
||||
ws: true,
|
||||
},
|
||||
'/favicon.ico': httpUrl,
|
||||
'/identicon': {
|
||||
target: httpUrl,
|
||||
rewrite(path) {
|
||||
return path.replace('@localhost:5173', '');
|
||||
},
|
||||
},
|
||||
'/url': httpUrl,
|
||||
'/proxy': httpUrl,
|
||||
'/_info_card_': httpUrl,
|
||||
'/bios': httpUrl,
|
||||
'/cli': httpUrl,
|
||||
'/inbox': httpUrl,
|
||||
'/emoji/': httpUrl,
|
||||
'/queue': httpUrl,
|
||||
'/notes': {
|
||||
target: httpUrl,
|
||||
headers: {
|
||||
'Accept': 'application/activity+json',
|
||||
},
|
||||
},
|
||||
'/users': {
|
||||
target: httpUrl,
|
||||
headers: {
|
||||
'Accept': 'application/activity+json',
|
||||
},
|
||||
},
|
||||
'/.well-known': {
|
||||
target: httpUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
...defaultConfig.build,
|
||||
rollupOptions: {
|
||||
...defaultConfig.build?.rollupOptions,
|
||||
input: 'index.html',
|
||||
},
|
||||
},
|
||||
|
||||
define: {
|
||||
...defaultConfig.define,
|
||||
_LANGS_FULL_: JSON.stringify(Object.entries(locales)),
|
||||
},
|
||||
};
|
||||
|
||||
export default defineConfig(({ command, mode }) => devConfig);
|
||||
|
|
@ -3,6 +3,8 @@ import pluginReplace from '@rollup/plugin-replace';
|
|||
import pluginVue from '@vitejs/plugin-vue';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { type UserConfig, defineConfig } from 'vite';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { promises as fsp } from 'fs';
|
||||
|
||||
import locales from '../../locales/index.js';
|
||||
import meta from '../../package.json';
|
||||
|
@ -10,6 +12,9 @@ import packageInfo from './package.json' with { type: 'json' };
|
|||
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
|
||||
import pluginJson5 from './vite.json5.js';
|
||||
|
||||
const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null;
|
||||
const host = url ? (new URL(url)).hostname : undefined;
|
||||
|
||||
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
|
||||
|
||||
/**
|
||||
|
@ -65,6 +70,7 @@ export function getConfig(): UserConfig {
|
|||
base: '/vite/',
|
||||
|
||||
server: {
|
||||
host,
|
||||
port: 5173,
|
||||
},
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue