Merge branch 'develop' into img-max

This commit is contained in:
tamaina
2023-04-09 01:44:25 +00:00
149 changed files with 2651 additions and 1472 deletions

View File

@@ -1,9 +1,7 @@
# (cd path/to/frontend; pnpm tsc -p .storybook)
# (cd path/to/frontend; node .storybook/generate.js)
/changes.js
/generate.js
# (cd path/to/frontend; node .storybook/preload-locale.js)
/preload-locale.js
/locale.ts
# (cd path/to/frontend; node .storybook/preload-theme.js)
/main.js
/preload-theme.js
/themes.ts

View File

@@ -0,0 +1,80 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import micromatch from 'micromatch';
import main from './main';
interface Stats {
readonly modules: readonly {
readonly id: string;
readonly name: string;
readonly reasons: readonly {
readonly moduleName: string;
}[];
}[];
}
fs.readFile(
path.resolve(__dirname, '../storybook-static/preview-stats.json')
).then((buffer) => {
const stats: Stats = JSON.parse(buffer.toString());
const keys = new Set(stats.modules.map((stat) => stat.id));
const map = new Map(
Array.from(keys, (key) => [
key,
new Set(
stats.modules
.filter((stat) => stat.id === key)
.flatMap((stat) => stat.reasons)
.map((reason) => reason.moduleName)
),
])
);
const modules = new Set(
process.argv
.slice(2)
.map((arg) =>
path.relative(
path.resolve(__dirname, '..'),
path.resolve(__dirname, '../../..', arg)
)
)
.map((path) => (path.startsWith('.') ? path : `./${path}`))
);
if (
micromatch(Array.from(modules), [
'../../assets/**',
'../../fluent-emojis/**',
'../../locales/**',
'../../misskey-assets/**',
'assets/**',
'public/**',
'../../pnpm-lock.yaml',
]).length
) {
return;
}
for (;;) {
const oldSize = modules.size;
for (const module of Array.from(modules)) {
if (map.has(module)) {
for (const dependency of Array.from(map.get(module)!)) {
modules.add(dependency);
}
}
}
if (modules.size === oldSize) {
break;
}
}
const stories = micromatch(
Array.from(modules),
main.stories.map((story) => `./${path.relative('..', story)}`)
);
if (stories.length) {
for (const story of stories) {
process.stdout.write(` --only-story-files ${story}`);
}
} else {
process.stdout.write(` --skip`);
}
});

View File

@@ -1,6 +1,7 @@
import { resolve } from 'node:path';
import type { StorybookConfig } from '@storybook/vue3-vite';
import { mergeConfig } from 'vite';
import turbosnap from 'vite-plugin-turbosnap';
const config = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
@@ -20,8 +21,13 @@ const config = {
core: {
disableTelemetry: true,
},
async viteFinal(config, options) {
async viteFinal(config) {
return mergeConfig(config, {
plugins: [
turbosnap({
rootDir: config.root ?? process.cwd(),
}),
],
build: {
target: [
'chrome108',

View File

@@ -1,4 +1,10 @@
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.12.0/tabler-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
<style>
html {
font-family: 'Hiragino Maru Gothic Pro', 'BIZ UDGothic', Roboto, HelveticaNeue, Arial, 'M PLUS Rounded 1c', sans-serif;
}
</style>
<script>
window.global = window;
</script>

View File

@@ -18,5 +18,10 @@
"jsx": "react",
"jsxFactory": "h"
},
"files": ["./generate.tsx", "./preload-locale.ts", "./preload-theme.ts"]
"files": [
"./changes.ts",
"./generate.tsx",
"./preload-locale.ts",
"./preload-theme.ts"
]
}

View File

@@ -58,13 +58,13 @@
"strict-event-emitter-types": "2.0.0",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.150.1",
"three": "0.151.3",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tsc-alias": "1.8.5",
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typescript": "5.0.2",
"typescript": "5.0.3",
"uuid": "9.0.0",
"vanilla-tilt": "1.8.0",
"vite": "4.2.1",
@@ -91,13 +91,14 @@
"@storybook/types": "7.0.2",
"@storybook/vue3": "7.0.2",
"@storybook/vue3-vite": "7.0.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/vue": "^6.6.1",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/vue": "7.0.0",
"@types/escape-regexp": "0.0.1",
"@types/estree": "^1.0.0",
"@types/estree": "1.0.0",
"@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1",
"@types/matter-js": "0.18.2",
"@types/micromatch": "3.1.1",
"@types/node": "18.15.11",
"@types/punycode": "2.1.0",
"@types/sanitize-html": "2.9.0",
@@ -108,32 +109,34 @@
"@types/uuid": "9.0.1",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.57.0",
"@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.1",
"@vitest/coverage-c8": "^0.29.8",
"@vue/runtime-core": "3.2.47",
"astring": "^1.8.4",
"chokidar-cli": "^3.0.0",
"chromatic": "^6.17.2",
"astring": "1.8.4",
"chokidar-cli": "3.0.0",
"chromatic": "6.17.3",
"cross-env": "7.0.3",
"cypress": "12.9.0",
"eslint": "8.37.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-vue": "9.10.0",
"fast-glob": "^3.2.12",
"fast-glob": "3.2.12",
"happy-dom": "8.9.0",
"msw": "^1.1.0",
"msw-storybook-addon": "^1.8.0",
"prettier": "^2.8.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"micromatch": "3.1.10",
"msw": "1.2.1",
"msw-storybook-addon": "1.8.0",
"prettier": "2.8.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"start-server-and-test": "2.0.0",
"storybook": "7.0.2",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly",
"vitest": "^0.29.8",
"vitest-fetch-mock": "^0.2.2",
"vue-eslint-parser": "9.1.0",
"vite-plugin-turbosnap": "^1.0.1",
"vitest": "0.29.8",
"vitest-fetch-mock": "0.2.2",
"vue-eslint-parser": "9.1.1",
"vue-tsc": "1.2.0"
}
}

View File

@@ -0,0 +1,32 @@
<template>
<div :class="$style.root">
<i class="ti ti-plane-departure" style="margin-right: 8px;"></i>
{{ i18n.ts.accountMoved }}
<MkMention :class="$style.link" :username="acct" :host="host ?? localHost"/>
</div>
</template>
<script lang="ts" setup>
import MkMention from './MkMention.vue';
import { i18n } from '@/i18n';
import { host as localHost } from '@/config';
defineProps<{
acct: string;
host: string;
}>();
</script>
<style lang="scss" module>
.root {
padding: 16px;
font-size: 90%;
background: var(--infoWarnBg);
color: var(--error);
border-radius: var(--radius);
}
.link {
margin-left: 4px;
}
</style>

View File

@@ -17,8 +17,8 @@
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
<template #caption>
<span v-if="okButtonDisabled && disabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })" />
<span v-else-if="okButtonDisabled && disabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })" />
<span v-if="okButtonDisabled && disabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/>
<span v-else-if="okButtonDisabled && disabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
</template>
</MkInput>
<MkSelect v-if="select" v-model="selectedValue" autofocus>
@@ -32,11 +32,11 @@
</template>
</MkSelect>
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
<MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
<MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
<MkButton v-if="showOkButton" inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
<MkButton v-if="showCancelButton || input || select" inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
</div>
<div v-if="actions" :class="$style.buttons">
<MkButton v-for="action in actions" :key="action.text" inline :primary="action.primary" :danger="action.danger" @click="() => { action.callback(); modal?.close(); }">{{ action.text }}</MkButton>
<MkButton v-for="action in actions" :key="action.text" inline rounded :primary="action.primary" :danger="action.danger" @click="() => { action.callback(); modal?.close(); }">{{ action.text }}</MkButton>
</div>
</div>
</MkModal>

View File

@@ -439,6 +439,7 @@ defineExpose({
&.asDrawer {
width: 100% !important;
padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
> .emojis {
::v-deep(section) {

View File

@@ -11,15 +11,21 @@
<div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown">
<div :class="[$style.header, { [$style.mini]: mini }]" @contextmenu.prevent.stop="onContextmenu">
<span :class="$style.headerLeft">
<button v-for="button in buttonsLeft" v-tooltip="button.title" class="_button" :class="[$style.headerButton, { [$style.highlighted]: button.highlighted }]" @click="button.onClick"><i :class="button.icon"></i></button>
<template v-if="!minimized">
<button v-for="button in buttonsLeft" v-tooltip="button.title" class="_button" :class="[$style.headerButton, { [$style.highlighted]: button.highlighted }]" @click="button.onClick"><i :class="button.icon"></i></button>
</template>
</span>
<span :class="$style.headerTitle" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown">
<slot name="header"></slot>
</span>
<span :class="$style.headerRight">
<button v-for="button in buttonsRight" v-tooltip="button.title" class="_button" :class="[$style.headerButton, { [$style.highlighted]: button.highlighted }]" @click="button.onClick"><i :class="button.icon"></i></button>
<template v-if="!minimized">
<button v-for="button in buttonsRight" v-tooltip="button.title" class="_button" :class="[$style.headerButton, { [$style.highlighted]: button.highlighted }]" @click="button.onClick"><i :class="button.icon"></i></button>
</template>
<button v-if="canResize && minimized" v-tooltip="i18n.ts.windowRestore" class="_button" :class="$style.headerButton" @click="unMinimize()"><i class="ti ti-maximize"></i></button>
<button v-else-if="canResize && !maximized" v-tooltip="i18n.ts.windowMinimize" class="_button" :class="$style.headerButton" @click="minimize()"><i class="ti ti-minimize"></i></button>
<button v-if="canResize && maximized" v-tooltip="i18n.ts.windowRestore" class="_button" :class="$style.headerButton" @click="unMaximize()"><i class="ti ti-picture-in-picture"></i></button>
<button v-else-if="canResize && !maximized" v-tooltip="i18n.ts.windowMaximize" class="_button" :class="$style.headerButton" @click="maximize()"><i class="ti ti-rectangle"></i></button>
<button v-else-if="canResize && !maximized && !minimized" v-tooltip="i18n.ts.windowMaximize" class="_button" :class="$style.headerButton" @click="maximize()"><i class="ti ti-rectangle"></i></button>
<button v-if="closeButton" v-tooltip="i18n.ts.close" class="_button" :class="$style.headerButton" @click="close()"><i class="ti ti-x"></i></button>
</span>
</div>
@@ -27,7 +33,7 @@
<slot></slot>
</div>
</div>
<template v-if="canResize">
<template v-if="canResize && !minimized">
<div :class="$style.handleTop" @mousedown.prevent="onTopHandleMousedown"></div>
<div :class="$style.handleRight" @mousedown.prevent="onRightHandleMousedown"></div>
<div :class="$style.handleBottom" @mousedown.prevent="onBottomHandleMousedown"></div>
@@ -100,10 +106,11 @@ let rootEl = $shallowRef<HTMLElement | null>();
let showing = $ref(true);
let beforeClickedAt = 0;
let maximized = $ref(false);
let unMaximizedTop = '';
let unMaximizedLeft = '';
let unMaximizedWidth = '';
let unMaximizedHeight = '';
let minimized = $ref(false);
let unResizedTop = '';
let unResizedLeft = '';
let unResizedWidth = '';
let unResizedHeight = '';
function close() {
showing = false;
@@ -132,10 +139,10 @@ function top() {
function maximize() {
maximized = true;
unMaximizedTop = rootEl.style.top;
unMaximizedLeft = rootEl.style.left;
unMaximizedWidth = rootEl.style.width;
unMaximizedHeight = rootEl.style.height;
unResizedTop = rootEl.style.top;
unResizedLeft = rootEl.style.left;
unResizedWidth = rootEl.style.width;
unResizedHeight = rootEl.style.height;
rootEl.style.top = '0';
rootEl.style.left = '0';
rootEl.style.width = '100%';
@@ -144,10 +151,35 @@ function maximize() {
function unMaximize() {
maximized = false;
rootEl.style.top = unMaximizedTop;
rootEl.style.left = unMaximizedLeft;
rootEl.style.width = unMaximizedWidth;
rootEl.style.height = unMaximizedHeight;
rootEl.style.top = unResizedTop;
rootEl.style.left = unResizedLeft;
rootEl.style.width = unResizedWidth;
rootEl.style.height = unResizedHeight;
}
function minimize() {
minimized = true;
unResizedWidth = rootEl.style.width;
unResizedHeight = rootEl.style.height;
rootEl.style.width = minWidth + 'px';
rootEl.style.height = props.mini ? '32px' : '39px';
}
function unMinimize() {
const main = rootEl;
if (main == null) return;
minimized = false;
rootEl.style.width = unResizedWidth;
rootEl.style.height = unResizedHeight;
const browserWidth = window.innerWidth;
const browserHeight = window.innerHeight;
const windowWidth = main.offsetWidth;
const windowHeight = main.offsetHeight;
const position = main.getBoundingClientRect();
if (position.top + windowHeight > browserHeight) main.style.top = browserHeight - windowHeight + 'px';
if (position.left + windowWidth > browserWidth) main.style.left = browserWidth - windowWidth + 'px';
}
function onBodyMousedown() {
@@ -155,7 +187,11 @@ function onBodyMousedown() {
}
function onDblClick() {
maximize();
if (minimized) {
unMinimize();
} else {
maximize();
}
}
function onHeaderMousedown(evt: MouseEvent) {
@@ -187,7 +223,7 @@ function onHeaderMousedown(evt: MouseEvent) {
const clickX = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientX : evt.clientX;
const clickY = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientY : evt.clientY;
const moveBaseX = beforeMaximized ? parseInt(unMaximizedWidth, 10) / 2 : clickX - position.left; // TODO: parseIntやめる
const moveBaseX = beforeMaximized ? parseInt(unResizedWidth, 10) / 2 : clickX - position.left; // TODO: parseIntやめる
const moveBaseY = beforeMaximized ? 20 : clickY - position.top;
const browserWidth = window.innerWidth;
const browserHeight = window.innerHeight;

View File

@@ -22,7 +22,7 @@ export const Default = {
};
},
},
template: '<MkA v-bind="props">Text</MkA>',
template: '<MkA v-bind="props">Misskey</MkA>',
};
},
async play({ canvasElement }) {

View File

@@ -4,12 +4,16 @@
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
<div v-if="user.isCat" :class="[$style.ears, { [$style.mask]: useBlurEffect }]">
<div :class="$style.earLeft">
<div v-if="useBlurEffect" :class="$style.layer">
<div v-if="false" :class="$style.layer">
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
</div>
</div>
<div :class="$style.earRight">
<div v-if="useBlurEffect" :class="$style.layer">
<div v-if="false" :class="$style.layer">
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
</div>
</div>
@@ -195,11 +199,21 @@ watch(() => props.user.avatarBlurhash, () => {
> .plot {
contain: strict;
position: absolute;
width: 100%;
height: 100%;
clip-path: path('M0 0H1V1H0z');
transform: scale(32767);
transform-origin: 0 0;
opacity: 0.5;
&:first-child {
opacity: 1;
}
&:last-child {
opacity: calc(1 / 3);
}
}
}
}
@@ -221,6 +235,14 @@ watch(() => props.user.avatarBlurhash, () => {
> .plot {
background-position: 20% 10%; /* ~= 37.5deg */
&:first-child {
background-position-x: 21%;
}
&:last-child {
background-position-y: 11%;
}
}
}
}
@@ -241,7 +263,16 @@ watch(() => props.user.avatarBlurhash, () => {
-38.5857864376%); /* 40 - 2 * sqrt(2) */
> .plot {
position: absolute;
background-position: 80% 10%; /* ~= 37.5deg */
&:first-child {
background-position-x: 79%;
}
&:last-child {
background-position-y: 11%;
}
}
}
}

View File

@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { waitFor } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
import MkPageHeader from './MkPageHeader.vue';
export const Empty = {
@@ -22,16 +23,16 @@ export const Empty = {
template: '<MkPageHeader v-bind="props" />',
};
},
async play() {
const wait = new Promise((resolve) => setTimeout(resolve, 800));
await waitFor(async () => await wait);
},
args: {
static: true,
tabs: [],
},
parameters: {
layout: 'centered',
chromatic: {
/* This component has animations that are implemented with JavaScript. So it's unstable to take a snapshot. */
disableSnapshot: true,
},
},
} satisfies StoryObj<typeof MkPageHeader>;
export const OneTab = {

View File

@@ -13,8 +13,6 @@ export class UserPreview {
this.el = el;
this.user = user;
this.attach();
this.show = this.show.bind(this);
this.close = this.close.bind(this);
this.onMouseover = this.onMouseover.bind(this);
@@ -22,6 +20,8 @@ export class UserPreview {
this.onClick = this.onClick.bind(this);
this.attach = this.attach.bind(this);
this.detach = this.detach.bind(this);
this.attach();
}
private show() {

View File

@@ -187,7 +187,7 @@ try {
} catch (err) {}
const app = createApp(
window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
new URLSearchParams(window.location.search).has('zen') ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
!$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) :
ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) :

View File

@@ -217,6 +217,7 @@ const patrons = [
'氷月氷華里',
'Ebise Lutica',
'巣黒るい@リスケモ男の娘VTuber!',
'ふぇいぽむ',
];
let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));

View File

@@ -113,16 +113,37 @@ function remove(ad) {
function save(ad) {
if (ad.id == null) {
os.apiWithDialog('admin/ad/create', {
os.api('admin/ad/create', {
...ad,
expiresAt: new Date(ad.expiresAt).getTime(),
startsAt: new Date(ad.startsAt).getTime(),
}).then(() => {
os.alert({
type: 'success',
text: i18n.ts.saved,
});
refresh();
}).catch(err => {
os.alert({
type: 'error',
text: err,
});
});
} else {
os.apiWithDialog('admin/ad/update', {
os.api('admin/ad/update', {
...ad,
expiresAt: new Date(ad.expiresAt).getTime(),
startsAt: new Date(ad.startsAt).getTime(),
}).then(() => {
os.alert({
type: 'success',
text: i18n.ts.saved,
});
}).catch(err => {
os.alert({
type: 'error',
text: err,
});
});
}
}
@@ -141,6 +162,25 @@ function more() {
}));
});
}
function refresh() {
os.api('admin/ad/list').then(adsResponse => {
ads = adsResponse.map(r => {
const exdate = new Date(r.expiresAt);
const stdate = new Date(r.startsAt);
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
...r,
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
});
});
}
refresh();
const headerActions = $computed(() => [{
asFullButton: true,
icon: 'ti ti-plus',

View File

@@ -69,6 +69,7 @@ function save(announcement) {
type: 'success',
text: i18n.ts.saved,
});
refresh();
}).catch(err => {
os.alert({
type: 'error',
@@ -90,6 +91,14 @@ function save(announcement) {
}
}
function refresh() {
os.api('admin/announcements/list').then(announcementResponse => {
announcements = announcementResponse;
});
}
refresh();
const headerActions = $computed(() => [{
asFullButton: true,
icon: 'ti ti-plus',

View File

@@ -2,7 +2,7 @@
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="700">
<div v-if="channel" class="_gaps_m">
<div v-if="channelId == null || channel != null" class="_gaps_m">
<MkInput v-model="name">
<template #label>{{ i18n.ts.name }}</template>
</MkInput>

View File

@@ -47,6 +47,7 @@ const featuredPagination = {
const favoritesPagination = {
endpoint: 'channels/my-favorites' as const,
limit: 100,
noPaging: true,
};
const followingPagination = {
endpoint: 'channels/followed' as const,

View File

@@ -88,7 +88,7 @@ const tagUsers = $computed(() => ({
},
}));
const pinnedUsers = { endpoint: 'pinned-users' };
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
state: 'alive',
origin: 'local',

View File

@@ -24,6 +24,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
const pagination = {
endpoint: 'antennas/list' as const,
noPaging: true,
limit: 10,
};

View File

@@ -32,6 +32,7 @@ import { clipsCache } from '@/cache';
const pagination = {
endpoint: 'clips/list' as const,
noPaging: true,
limit: 10,
};

View File

@@ -30,6 +30,7 @@ const pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>();
const pagination = {
endpoint: 'users/lists/list' as const,
noPaging: true,
limit: 10,
};

View File

@@ -130,11 +130,6 @@ const menuDef = computed(() => [{
}, {
title: i18n.ts.otherSettings,
items: [{
icon: 'ti ti-package',
text: i18n.ts.importAndExport,
to: '/settings/import-export',
active: currentPage?.route.name === 'import-export',
}, {
icon: 'ti ti-badges',
text: i18n.ts.roles,
to: '/settings/roles',
@@ -165,6 +160,16 @@ const menuDef = computed(() => [{
to: '/settings/webhook',
active: currentPage?.route.name === 'webhook',
}, {
icon: 'ti ti-package',
text: i18n.ts.importAndExport,
to: '/settings/import-export',
active: currentPage?.route.name === 'import-export',
}, /*{
icon: 'ti ti-plane',
text: i18n.ts.accountMigration,
to: '/settings/migration',
active: currentPage?.route.name === 'migration',
},*/ {
icon: 'ti ti-dots',
text: i18n.ts.other,
to: '/settings/other',
@@ -231,7 +236,7 @@ onUnmounted(() => {
});
watch(router.currentRef, (to) => {
if (to.route.name === "settings" && to.child?.route.name == null && !narrow) {
if (to.route.name === 'settings' && to.child?.route.name == null && !narrow) {
router.replace('/settings/profile');
}
});

View File

@@ -0,0 +1,73 @@
<template>
<div class="_gaps_m">
<FormSection first>
<template #label>{{ i18n.ts._accountMigration.moveTo }}</template>
<MkInput v-model="moveToAccount" manual-save>
<template #prefix><i class="ti ti-plane-departure"></i></template>
<template #label>{{ i18n.ts._accountMigration.moveToLabel }}</template>
</MkInput>
</FormSection>
<FormInfo warn>{{ i18n.ts._accountMigration.moveAccountDescription }}</FormInfo>
<FormSection>
<template #label>{{ i18n.ts._accountMigration.moveFrom }}</template>
<MkInput v-model="accountAlias" manual-save>
<template #prefix><i class="ti ti-plane-arrival"></i></template>
<template #label>{{ i18n.ts._accountMigration.moveFromLabel }}</template>
</MkInput>
</FormSection>
<FormInfo warn>{{ i18n.ts._accountMigration.moveFromDescription }}</FormInfo>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import FormSection from '@/components/form/section.vue';
import FormInfo from '@/components/MkInfo.vue';
import MkInput from '@/components/MkInput.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
const moveToAccount = ref('');
const accountAlias = ref('');
async function move(): Promise<void> {
const account = moveToAccount.value;
const confirm = await os.confirm({
type: 'warning',
text: i18n.t('migrationConfirm', { account: account.toString() }),
});
if (confirm.canceled) return;
os.apiWithDialog('i/move', {
moveToAccount: account,
});
}
async function save(): Promise<void> {
const account = accountAlias.value;
os.apiWithDialog('i/known-as', {
alsoKnownAs: account,
});
}
watch(accountAlias, async () => {
await save();
});
watch(moveToAccount, async () => {
await move();
});
definePageMetadata({
title: i18n.ts.accountMigration,
icon: 'ti ti-plane',
});
</script>
<style lang="scss">
.description {
font-size: .85em;
padding: 1rem;
}
</style>

View File

@@ -7,6 +7,7 @@
<!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> -->
<div class="profile _gaps">
<MkAccountMoved v-if="user.movedToUri" :host="user.movedToUri.host" :acct="user.movedToUri.username"/>
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
<div :key="user.id" class="main _panel">
@@ -117,6 +118,7 @@ import calcAge from 's-age';
import * as misskey from 'misskey-js';
import MkNote from '@/components/MkNote.vue';
import MkFollowButton from '@/components/MkFollowButton.vue';
import MkAccountMoved from '@/components/MkAccountMoved.vue';
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
import MkOmit from '@/components/MkOmit.vue';
import MkInfo from '@/components/MkInfo.vue';

View File

@@ -161,6 +161,10 @@ export const routes = [{
path: '/preferences-backups',
name: 'preferences-backups',
component: page(() => import('./pages/settings/preferences-backups.vue')),
}, {
path: '/migration',
name: 'migration',
component: page(() => import('./pages/settings/migration.vue'))
}, {
path: '/custom-css',
name: 'general',

View File

@@ -127,6 +127,7 @@ hr {
}
.ti {
width: 1.28em;
vertical-align: -12%;
line-height: 1em;