@@ -162,6 +164,7 @@ import { claimAchievement } from '@/scripts/achievements';
import { getNoteSummary } from '@/scripts/get-note-summary';
import { MenuItem } from '@/types/menu';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
+import { showMovedDialog } from '@/scripts/show-moved-dialog';
const props = defineProps<{
note: misskey.entities.Note;
@@ -255,6 +258,7 @@ useTooltip(renoteButton, async (showing) => {
function renote(viaKeyboard = false) {
pleaseLogin();
+ showMovedDialog();
let items = [] as MenuItem[];
@@ -335,6 +339,7 @@ function reply(viaKeyboard = false): void {
function react(viaKeyboard = false): void {
pleaseLogin();
+ showMovedDialog();
if (appearNote.reactionAcceptance === 'likeOnly') {
os.api('notes/reactions/create', {
noteId: appearNote.id,
@@ -401,6 +406,7 @@ async function clip() {
function showRenoteMenu(viaKeyboard = false): void {
if (!isMyRenote) return;
+ pleaseLogin();
os.popupMenu([{
text: i18n.ts.unrenote,
icon: 'ti ti-trash',
@@ -484,6 +490,11 @@ function showReactions(): void {
}
}
+ .footer {
+ position: relative;
+ z-index: 1;
+ }
+
&:hover > .article > .main > .footer > .footerButton {
opacity: 1;
}
@@ -537,6 +548,7 @@ function showReactions(): void {
}
.renote {
+ position: relative;
display: flex;
align-items: center;
padding: 16px 32px 8px 32px;
@@ -547,6 +559,10 @@ function showReactions(): void {
& + .article {
padding-top: 8px;
}
+
+ > .colorBar {
+ height: calc(100% - 6px);
+ }
}
.renoteAvatar {
@@ -618,6 +634,16 @@ function showReactions(): void {
padding: 28px 32px;
}
+.colorBar {
+ position: absolute;
+ top: 8px;
+ left: 8px;
+ width: 5px;
+ height: calc(100% - 16px);
+ border-radius: 999px;
+ pointer-events: none;
+}
+
.avatar {
flex-shrink: 0;
display: block !important;
@@ -669,6 +695,7 @@ function showReactions(): void {
position: absolute;
bottom: 0;
left: 0;
+ z-index: 2;
width: 100%;
height: 64px;
background: linear-gradient(0deg, var(--panel), var(--X15));
@@ -833,6 +860,13 @@ function showReactions(): void {
}
}
}
+
+ .colorBar {
+ top: 6px;
+ left: 6px;
+ width: 4px;
+ height: calc(100% - 12px);
+ }
}
@container (max-width: 300px) {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index b9ab36685..0d6d329d9 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -166,6 +166,7 @@ import { useTooltip } from '@/scripts/use-tooltip';
import { claimAchievement } from '@/scripts/achievements';
import { MenuItem } from '@/types/menu';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
+import { showMovedDialog } from '@/scripts/show-moved-dialog';
const props = defineProps<{
note: misskey.entities.Note;
@@ -248,6 +249,7 @@ useTooltip(renoteButton, async (showing) => {
function renote(viaKeyboard = false) {
pleaseLogin();
+ showMovedDialog();
let items = [] as MenuItem[];
@@ -318,6 +320,7 @@ function renote(viaKeyboard = false) {
function reply(viaKeyboard = false): void {
pleaseLogin();
+ showMovedDialog();
os.post({
reply: appearNote,
animation: !viaKeyboard,
@@ -328,6 +331,7 @@ function reply(viaKeyboard = false): void {
function react(viaKeyboard = false): void {
pleaseLogin();
+ showMovedDialog();
if (appearNote.reactionAcceptance === 'likeOnly') {
os.api('notes/reactions/create', {
noteId: appearNote.id,
@@ -394,6 +398,7 @@ async function clip() {
function showRenoteMenu(viaKeyboard = false): void {
if (!isMyRenote) return;
+ pleaseLogin();
os.popupMenu([{
text: i18n.ts.unrenote,
icon: 'ti ti-trash',
diff --git a/packages/frontend/src/components/MkNumberDiff.vue b/packages/frontend/src/components/MkNumberDiff.vue
index e7d4a5472..303417dae 100644
--- a/packages/frontend/src/components/MkNumberDiff.vue
+++ b/packages/frontend/src/components/MkNumberDiff.vue
@@ -1,47 +1,32 @@
-
+
{{ isPlus ? '+' : '' }}{{ number(value) }}
-
-
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index 0f148022b..e2d68d12c 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<{
maxHeight: 200,
});
-let content = $ref
();
+let content = $shallowRef();
let omitted = $ref(false);
let ignoreOmit = $ref(false);
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 42a3748d9..c65cb7d6e 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -247,6 +247,10 @@ watch($$(text), () => {
checkMissingMention();
}, { immediate: true });
+watch($$(visibility), () => {
+ checkMissingMention();
+}, { immediate: true });
+
watch($$(visibleUsers), () => {
checkMissingMention();
}, {
@@ -900,27 +904,28 @@ defineExpose({
}
.headerLeft {
- display: grid;
- grid-template-columns: repeat(2, minmax(36px, 50px));
- grid-template-rows: minmax(40px, 100%);
+ display: flex;
+ flex: 0 1 100px;
}
.cancel {
padding: 0;
font-size: 1em;
height: 100%;
+ flex: 0 1 50px;
}
.account {
height: 100%;
display: inline-flex;
vertical-align: bottom;
+ flex: 0 1 50px;
}
.avatar {
width: 28px;
height: 28px;
- margin: auto 0;
+ margin: auto;
}
.headerRight {
diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue
index fcf454c77..5db2f5ee6 100644
--- a/packages/frontend/src/components/MkRadio.vue
+++ b/packages/frontend/src/components/MkRadio.vue
@@ -24,7 +24,7 @@ import { } from 'vue';
const props = defineProps<{
modelValue: any;
value: any;
- disabled: boolean;
+ disabled?: boolean;
}>();
const emit = defineEmits<{
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index 8590ccf9a..e2240fb4e 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -1,5 +1,5 @@
+
+
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index 85c009f74..f33f68cab 100644
--- a/packages/frontend/src/components/MkRetentionHeatmap.vue
+++ b/packages/frontend/src/components/MkRetentionHeatmap.vue
@@ -44,7 +44,13 @@ async function renderChart() {
const data = [];
for (const record of raw) {
- let i = 0;
+ data.push({
+ x: 0,
+ y: record.createdAt,
+ v: record.users,
+ });
+
+ let i = 1;
for (const date of Object.keys(record.data).sort((a, b) => new Date(a).getTime() - new Date(b).getTime())) {
data.push({
x: i,
@@ -61,8 +67,14 @@ async function renderChart() {
const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300';
- // 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする
- const max = raw.map(x => x.users).slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
+ const getYYYYMMDD = (date: Date) => {
+ const y = date.getFullYear().toString().padStart(2, '0');
+ const m = (date.getMonth() + 1).toString().padStart(2, '0');
+ const d = date.getDate().toString().padStart(2, '0');
+ return `${y}/${m}/${d}`;
+ };
+
+ const max = (createdAt: string) => raw.find(x => x.createdAt === createdAt)!.users;
const marginEachCell = 12;
@@ -78,7 +90,7 @@ async function renderChart() {
borderRadius: 3,
backgroundColor(c) {
const value = c.dataset.data[c.dataIndex].v;
- const a = value / max;
+ const a = value / max(c.dataset.data[c.dataIndex].y);
return alpha(color, a);
},
fill: true,
@@ -115,7 +127,7 @@ async function renderChart() {
maxRotation: 0,
autoSkipPadding: 0,
autoSkip: false,
- callback: (value, index, values) => value + 1,
+ callback: (value, index, values) => value,
},
},
y: {
@@ -150,11 +162,11 @@ async function renderChart() {
callbacks: {
title(context) {
const v = context[0].dataset.data[context[0].dataIndex];
- return v.d;
+ return getYYYYMMDD(new Date(new Date(v.y).getTime() + (v.x * 86400000)));
},
label(context) {
const v = context.dataset.data[context.dataIndex];
- return ['Active: ' + v.v];
+ return [`Active: ${v.v} (${Math.round((v.v / max(v.y)) * 100)}%)`];
},
},
//mode: 'index',
diff --git a/packages/frontend/src/components/MkSample.vue b/packages/frontend/src/components/MkSample.vue
index 7a3bc2088..922b862b4 100644
--- a/packages/frontend/src/components/MkSample.vue
+++ b/packages/frontend/src/components/MkSample.vue
@@ -87,7 +87,7 @@ export default defineComponent({
},
async openDrive() {
- os.selectDriveFile();
+ os.selectDriveFile(false);
},
async selectUser() {
diff --git a/packages/frontend/src/components/MkSignup.vue b/packages/frontend/src/components/MkSignup.vue
deleted file mode 100644
index 30279148f..000000000
--- a/packages/frontend/src/components/MkSignup.vue
+++ /dev/null
@@ -1,263 +0,0 @@
-
-
-
-
-
-
-
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
new file mode 100644
index 000000000..0e8bdb321
--- /dev/null
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -0,0 +1,272 @@
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
new file mode 100644
index 000000000..2d9545573
--- /dev/null
+++ b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
@@ -0,0 +1,94 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { expect } from '@storybook/jest';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { StoryObj } from '@storybook/vue3';
+import { onBeforeUnmount } from 'vue';
+import MkSignupServerRules from './MkSignupDialog.rules.vue';
+import { i18n } from '@/i18n';
+import { instance } from '@/instance';
+export const Empty = {
+ render(args) {
+ return {
+ components: {
+ MkSignupServerRules,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '',
+ };
+ },
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const groups = await canvas.findAllByRole('group');
+ const buttons = await canvas.findAllByRole('button');
+ for (const group of groups) {
+ if (group.ariaExpanded === 'true') {
+ continue;
+ }
+ const button = await within(group).findByRole('button');
+ userEvent.click(button);
+ await waitFor(() => expect(group).toHaveAttribute('aria-expanded', 'true'));
+ }
+ const labels = await canvas.findAllByText(i18n.ts.agree);
+ for (const label of labels) {
+ expect(buttons.at(-1)).toBeDisabled();
+ await waitFor(() => userEvent.click(label));
+ }
+ expect(buttons.at(-1)).toBeEnabled();
+ },
+ args: {
+ serverRules: [],
+ tosUrl: null,
+ },
+ decorators: [
+ (_, context) => ({
+ setup() {
+ instance.serverRules = context.args.serverRules;
+ instance.tosUrl = context.args.tosUrl;
+ onBeforeUnmount(() => {
+ // FIXME: 呼び出されない
+ instance.serverRules = [];
+ instance.tosUrl = null;
+ });
+ },
+ template: '',
+ }),
+ ],
+ parameters: {
+ layout: 'centered',
+ },
+} satisfies StoryObj;
+export const ServerRulesOnly = {
+ ...Empty,
+ args: {
+ ...Empty.args,
+ serverRules: [
+ 'ルール',
+ ],
+ },
+} satisfies StoryObj;
+export const TOSOnly = {
+ ...Empty,
+ args: {
+ ...Empty.args,
+ tosUrl: 'https://example.com/tos',
+ },
+} satisfies StoryObj;
+export const ServerRulesAndTOS = {
+ ...Empty,
+ args: {
+ ...Empty.args,
+ serverRules: ServerRulesOnly.args.serverRules,
+ tosUrl: TOSOnly.args.tosUrl,
+ },
+} satisfies StoryObj;
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue
new file mode 100644
index 000000000..6da81c3bc
--- /dev/null
+++ b/packages/frontend/src/components/MkSignupDialog.rules.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+ {{ i18n.ts.invitationRequiredToRegister }}
+
+
+
{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}
+
+
+ {{ i18n.ts.serverRules }}
+
+
+
+
+
+
+ {{ i18n.ts.agree }}
+
+
+
+ {{ i18n.ts.termsOfService }}
+
+
+ {{ i18n.ts.termsOfService }}
+
+ {{ i18n.ts.agree }}
+
+
+
+ {{ i18n.ts.basicNotesBeforeCreateAccount }}
+
+
+ {{ i18n.ts.basicNotesBeforeCreateAccount }}
+
+ {{ i18n.ts.agree }}
+
+
+
{{ i18n.ts.pleaseAgreeAllToContinue }}
+
+
+ {{ i18n.ts.cancel }}
+ {{ i18n.ts.continue }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 790c1e94d..17f8b8642 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -1,24 +1,40 @@
{{ i18n.ts.signup }}
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue
index 8bb8637dd..d9f6716f9 100644
--- a/packages/frontend/src/components/MkSwitch.vue
+++ b/packages/frontend/src/components/MkSwitch.vue
@@ -9,7 +9,7 @@
:disabled="disabled"
@keydown.enter="toggle"
>
-
+
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index 5086c1b31..6349ada65 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -1,30 +1,30 @@
-
-
-
-
-
-
+
+
+
+
-
{{ i18n.ts.followsYou }}
-
+
{{ i18n.ts.followsYou }}
+
{{ i18n.ts.noAccountDescription }}
-
-
-
{{ i18n.ts.notes }}
{{ user.notesCount }}
+
+
+
{{ i18n.ts.notes }}
{{ user.notesCount }}
-
-
{{ i18n.ts.following }}
{{ user.followingCount }}
+
+
{{ i18n.ts.following }}
{{ user.followingCount }}
-
-
{{ i18n.ts.followers }}
{{ user.followersCount }}
+
+
{{ i18n.ts.followers }}
{{ user.followersCount }}
-
+
@@ -40,99 +40,99 @@ defineProps<{
}>();
-
diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue
index 51eb426e9..3571ca84d 100644
--- a/packages/frontend/src/components/MkUserList.vue
+++ b/packages/frontend/src/components/MkUserList.vue
@@ -8,7 +8,7 @@
-
+
@@ -29,8 +29,8 @@ const props = withDefaults(defineProps<{
});
-
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
new file mode 100644
index 000000000..f4930aa26
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
@@ -0,0 +1,31 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import MkUserSetupDialog_Profile from './MkUserSetupDialog.Profile.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkUserSetupDialog_Profile,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '
',
+ };
+ },
+ args: {
+
+ },
+ parameters: {
+ layout: 'centered',
+ },
+} satisfies StoryObj
;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
new file mode 100644
index 000000000..adb8d4334
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
@@ -0,0 +1,101 @@
+
+
+
{{ i18n.ts._initialAccountSetting.theseSettingsCanEditLater }}
+
+
+ {{ i18n.ts.avatar }}
+
+
+
+ {{ i18n.ts._profile.changeAvatar }}
+
+
+
+
+
+ {{ i18n.ts._profile.name }}
+
+
+
+ {{ i18n.ts._profile.description }}
+
+
+
{{ i18n.ts._initialAccountSetting.youCanEditMoreSettingsInSettingsPageLater }}
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
new file mode 100644
index 000000000..7413f4884
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
@@ -0,0 +1,32 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { userDetailed } from '../../.storybook/fakes';
+import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkUserSetupDialog_User,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '',
+ };
+ },
+ args: {
+ user: userDetailed(),
+ },
+ parameters: {
+ layout: 'centered',
+ },
+} satisfies StoryObj;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue
new file mode 100644
index 000000000..d66f34f16
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
{{ i18n.ts.noAccountDescription }}
+
+
+
{{ i18n.ts.follow }}
+
{{ i18n.ts.youFollowing }}
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
new file mode 100644
index 000000000..55790602d
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
@@ -0,0 +1,51 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { commonHandlers } from '../../.storybook/mocks';
+import { userDetailed } from '../../.storybook/fakes';
+import MkUserSetupDialog from './MkUserSetupDialog.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkUserSetupDialog,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '',
+ };
+ },
+ args: {
+
+ },
+ parameters: {
+ layout: 'centered',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/users', (req, res, ctx) => {
+ return res(ctx.json([
+ userDetailed('44'),
+ userDetailed('49'),
+ ]));
+ }),
+ rest.post('/api/pinned-users', (req, res, ctx) => {
+ return res(ctx.json([
+ userDetailed('44'),
+ userDetailed('49'),
+ ]));
+ }),
+ ],
+ },
+ },
+} satisfies StoryObj;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
new file mode 100644
index 000000000..096b88c30
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -0,0 +1,145 @@
+
+
+ {{ i18n.ts.initialAccountSetting }}
+
+
+
+
+
+
+
+
+
{{ i18n.ts._initialAccountSetting.accountCreated }}
+
{{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}
+
{{ i18n.ts._initialAccountSetting.profileSetting }}
+
+
+
+
+
+
+
+
+ {{ i18n.ts.continue }}
+
+
+
+
+
+
+
+ {{ i18n.ts.continue }}
+
+
+
+
+
+
+
+
+
{{ i18n.ts.pushNotification }}
+
{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}
+
+
{{ i18n.ts.continue }}
+
+
+
+
+
+
+
+
+
+
{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}
+
+ {{ instance.name ?? host }}
+
+ {{ i18n.ts.help }}
+
+
+
{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}
+
{{ i18n.ts.close }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
new file mode 100644
index 000000000..fb705786c
--- /dev/null
+++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
new file mode 100644
index 000000000..622676812
--- /dev/null
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+ {{ instanceName }}
+
+
+
+ {{ i18n.ts.invitationRequiredToRegister }}
+
+
+ {{ i18n.ts.joinThisServer }}
+ {{ i18n.ts.exploreOtherServers }}
+ {{ i18n.ts.login }}
+
+
+
+
+
+
{{ i18n.ts.users }}
+
+
+
+
{{ i18n.ts.notes }}
+
+
+
+
+
{{ i18n.ts.letsLookAtTimeline }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index d074fdd15..33e594acd 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -2,11 +2,11 @@
-
+
{{ i18n.ts.selectWidget }}
- {{ i18n.ts.add }}
+ {{ i18n.ts.add }}
{{ i18n.ts.close }}
-
@@ -541,7 +541,7 @@ defineExpose({
flex: 1;
overflow: auto;
background: var(--panel);
- container-type: inline-size;
+ container-type: size;
}
$handleSize: 8px;
diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
index d5e3fc356..9d5fd3947 100644
--- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
@@ -41,3 +41,35 @@ export const Detail = {
detail: true,
},
} satisfies StoryObj;
+export const Long = {
+ ...Default,
+ args: {
+ ...Default.args,
+ user: {
+ ...userDetailed(),
+ username: 'the_quick_brown_fox_jumped_over_the_lazy_dog',
+ host: 'misskey.example',
+ },
+ },
+ decorators: [
+ () => ({
+ template: '
',
+ }),
+ ],
+} satisfies StoryObj;
+export const VeryLong = {
+ ...Default,
+ args: {
+ ...Default.args,
+ user: {
+ ...userDetailed(),
+ username: '2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc',
+ host: 'the.quick.brown.fox.jumped.over.the.lazy.dog.very.long.hostname.nostr.example',
+ },
+ },
+ decorators: [
+ () => ({
+ template: '
',
+ }),
+ ],
+} satisfies StoryObj;
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index 2b9f892fc..59358aef7 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -1,5 +1,9 @@
-
+
+ @{{ user.username }}
+ @{{ user.host || host }}
+
+
@{{ user.username }}
@{{ user.host || host }}
@@ -8,6 +12,7 @@
+
+
+
+
diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts
index 60ac5c91a..8252a4d76 100644
--- a/packages/frontend/src/components/global/MkError.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkError.stories.impl.ts
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
import { expect } from '@storybook/jest';
import { waitFor } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
@@ -20,14 +21,21 @@ export const Default = {
...this.args,
};
},
+ events() {
+ return {
+ retry: action('retry'),
+ };
+ },
},
- template: '',
+ template: '',
};
},
async play({ canvasElement }) {
await expect(canvasElement.firstElementChild).not.toBeNull();
await waitFor(async () => expect(canvasElement.firstElementChild?.classList).not.toContain('_transition_zoom-enter-active'));
},
+ args: {
+ },
parameters: {
layout: 'centered',
},
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 710edd797..b91d378b1 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -156,7 +156,7 @@ onUnmounted(() => {
}
&.thin {
- --height: 42px;
+ --height: 40px;
> .buttons {
> .button {
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 99169512d..261cc0ee1 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -8,6 +8,7 @@
-
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index b742132af..d51bf6230 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -96,7 +96,9 @@ async function testEmail() {
const { canceled, result: destination } = await os.inputText({
title: i18n.ts.destination,
type: 'email',
- placeholder: instance.maintainerEmail,
+ default: instance.maintainerEmail ?? '',
+ placeholder: 'test@example.com',
+ minLength: 1,
});
if (canceled) return;
os.apiWithDialog('admin/send-email', {
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index ebe1a8ade..ffd3b6e23 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -5,14 +5,30 @@
-
-
-
- {{ i18n.ts.sensitiveWords }}
- {{ i18n.ts.sensitiveWordsDescription }}
-
-
-
+
+ {{ i18n.ts.enableRegistration }}
+
+
+
+ {{ i18n.ts.emailRequiredForSignup }}
+
+
+
{{ i18n.ts.serverRules }}
+
+
+
+ {{ i18n.ts.tosUrl }}
+
+
+
+ {{ i18n.ts.preservedUsernames }}
+ {{ i18n.ts.preservedUsernamesDescription }}
+
+
+
+ {{ i18n.ts.sensitiveWords }}
+ {{ i18n.ts.sensitiveWordsDescription }}
+
@@ -41,17 +57,30 @@ import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkButton from '@/components/MkButton.vue';
+import FormLink from '@/components/form/link.vue';
+let enableRegistration: boolean = $ref(false);
+let emailRequiredForSignup: boolean = $ref(false);
let sensitiveWords: string = $ref('');
+let preservedUsernames: string = $ref('');
+let tosUrl: string | null = $ref(null);
async function init() {
const meta = await os.api('admin/meta');
+ enableRegistration = !meta.disableRegistration;
+ emailRequiredForSignup = meta.emailRequiredForSignup;
sensitiveWords = meta.sensitiveWords.join('\n');
+ preservedUsernames = meta.preservedUsernames.join('\n');
+ tosUrl = meta.tosUrl;
}
function save() {
os.apiWithDialog('admin/update-meta', {
+ disableRegistration: !enableRegistration,
+ emailRequiredForSignup,
+ tosUrl,
sensitiveWords: sensitiveWords.split('\n'),
+ preservedUsernames: preservedUsernames.split('\n'),
}).then(() => {
fetchInstance();
});
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index b1aa03f1f..c211ef2f0 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -54,6 +54,7 @@ if (props.id) {
target: 'manual',
condFormula: { id: uuid(), type: 'isRemote' },
isPublic: false,
+ isExplorable: false,
asBadge: false,
canEditMembersByModerator: false,
displayOrder: 0,
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 873ff02fe..49942c87c 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -8,10 +8,9 @@
{{ i18n.ts._role.description }}
-
+
{{ i18n.ts.color }}
- #RRGGBB
-
+
{{ i18n.ts._role.iconUrl }}
@@ -59,6 +58,11 @@
{{ i18n.ts._role.descriptionOfAsBadge }}
+
+ {{ i18n.ts._role.isExplorable }}
+ {{ i18n.ts._role.descriptionOfIsExplorable }}
+
+
{{ i18n.ts._role.policies }}
@@ -206,7 +210,7 @@
-
+
{{ i18n.ts._role._options.driveCapacity }}
@@ -227,6 +231,26 @@
+
+ {{ i18n.ts._role._options.alwaysMarkNsfw }}
+
+ {{ i18n.ts._role.useBaseValue }}
+ {{ role.policies.alwaysMarkNsfw.value ? i18n.ts.yes : i18n.ts.no }}
+
+
+
+
+ {{ i18n.ts._role.useBaseValue }}
+
+
+ {{ i18n.ts.enable }}
+
+
+ {{ i18n.ts._role.priority }}
+
+
+
+
{{ i18n.ts._role._options.pinMax }}
@@ -409,6 +433,7 @@ import { watch } from 'vue';
import { throttle } from 'throttle-debounce';
import RolesEditorFormula from './RolesEditorFormula.vue';
import MkInput from '@/components/MkInput.vue';
+import MkColorInput from '@/components/MkColorInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkFolder from '@/components/MkFolder.vue';
@@ -475,6 +500,7 @@ const save = throttle(100, () => {
isAdministrator: role.isAdministrator,
isModerator: role.isModerator,
isPublic: role.isPublic,
+ isExplorable: role.isExplorable,
asBadge: role.asBadge,
canEditMembersByModerator: role.canEditMembersByModerator,
policies: role.policies,
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index a1e467edb..e8dbe1c5f 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -75,6 +75,14 @@
+
+ {{ i18n.ts._role._options.alwaysMarkNsfw }}
+ {{ policies.alwaysMarkNsfw ? i18n.ts.yes : i18n.ts.no }}
+
+ {{ i18n.ts.enable }}
+
+
+
{{ i18n.ts._role._options.pinMax }}
{{ policies.pinLimit }}
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
new file mode 100644
index 000000000..85781c0bd
--- /dev/null
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
{{ i18n.ts._serverRules.description }}
+
e.item.classList.add('active')"
+ @end="e => e.item.classList.remove('active')"
+ >
+
+
+
+
+
+ {{ i18n.ts.add }}
+ {{ i18n.ts.save }}
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 65e64930d..7ec3c381f 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -13,11 +13,6 @@
{{ i18n.ts.instanceDescription }}
-
-
- {{ i18n.ts.tosUrl }}
-
-
{{ i18n.ts.maintainerName }}
@@ -36,14 +31,6 @@
-
- {{ i18n.ts.enableRegistration }}
-
-
-
- {{ i18n.ts.emailRequiredForSignup }}
-
-
{{ i18n.ts.enableChartsForRemoteUser }}
@@ -73,11 +60,9 @@
{{ i18n.ts.backgroundImageUrl }}
-
-
+
{{ i18n.ts.themeColor }}
- #RRGGBB
-
+
{{ i18n.ts.instanceDefaultLightTheme }}
@@ -166,10 +151,10 @@ import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkButton from '@/components/MkButton.vue';
+import MkColorInput from '@/components/MkColorInput.vue';
let name: string | null = $ref(null);
let description: string | null = $ref(null);
-let tosUrl: string | null = $ref(null);
let maintainerName: string | null = $ref(null);
let maintainerEmail: string | null = $ref(null);
let iconUrl: string | null = $ref(null);
@@ -180,8 +165,6 @@ let defaultLightTheme: any = $ref(null);
let defaultDarkTheme: any = $ref(null);
let pinnedUsers: string = $ref('');
let cacheRemoteFiles: boolean = $ref(false);
-let enableRegistration: boolean = $ref(false);
-let emailRequiredForSignup: boolean = $ref(false);
let enableServiceWorker: boolean = $ref(false);
let enableChartsForRemoteUser: boolean = $ref(false);
let enableChartsForFederatedInstances: boolean = $ref(false);
@@ -194,7 +177,6 @@ async function init() {
const meta = await os.api('admin/meta');
name = meta.name;
description = meta.description;
- tosUrl = meta.tosUrl;
iconUrl = meta.iconUrl;
bannerUrl = meta.bannerUrl;
backgroundImageUrl = meta.backgroundImageUrl;
@@ -205,8 +187,6 @@ async function init() {
maintainerEmail = meta.maintainerEmail;
pinnedUsers = meta.pinnedUsers.join('\n');
cacheRemoteFiles = meta.cacheRemoteFiles;
- enableRegistration = !meta.disableRegistration;
- emailRequiredForSignup = meta.emailRequiredForSignup;
enableServiceWorker = meta.enableServiceWorker;
enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
@@ -220,7 +200,6 @@ function save() {
os.apiWithDialog('admin/update-meta', {
name,
description,
- tosUrl,
iconUrl,
bannerUrl,
backgroundImageUrl,
@@ -231,8 +210,6 @@ function save() {
maintainerEmail,
pinnedUsers: pinnedUsers.split('\n'),
cacheRemoteFiles,
- disableRegistration: !enableRegistration,
- emailRequiredForSignup,
enableServiceWorker,
enableChartsForRemoteUser,
enableChartsForFederatedInstances,
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 9cb440d2b..a74ab4047 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -11,6 +11,10 @@
{{ i18n.ts.description }}
+
+ {{ i18n.ts.color }}
+
+
{{ i18n.ts._channel.setBanner }}
@@ -42,8 +46,9 @@
-
+
{{ channelId ? i18n.ts.save : i18n.ts.create }}
+ {{ i18n.ts.archive }}
@@ -55,6 +60,7 @@ import { computed, ref, watch, defineAsyncComponent } from 'vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
+import MkColorInput from '@/components/MkColorInput.vue';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
import { useRouter } from '@/router';
@@ -75,6 +81,7 @@ let name = $ref(null);
let description = $ref(null);
let bannerUrl = $ref
(null);
let bannerId = $ref(null);
+let color = $ref('#000');
const pinnedNotes = ref([]);
watch(() => bannerId, async () => {
@@ -101,6 +108,7 @@ async function fetchChannel() {
pinnedNotes.value = channel.pinnedNoteIds.map(id => ({
id,
}));
+ color = channel.color;
}
fetchChannel();
@@ -128,6 +136,7 @@ function save() {
description: description,
bannerId: bannerId,
pinnedNoteIds: pinnedNotes.value.map(x => x.id),
+ color: color,
};
if (props.channelId) {
@@ -143,6 +152,23 @@ function save() {
}
}
+async function archive() {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ title: i18n.t('channelArchiveConfirmTitle', { name: name }),
+ text: i18n.ts.channelArchiveConfirmDescription,
+ });
+
+ if (canceled) return;
+
+ os.api('channels/update', {
+ channelId: props.channelId,
+ isArchived: true,
+ }).then(() => {
+ os.success();
+ });
+}
+
function setBannerImage(evt) {
selectFile(evt.currentTarget ?? evt.target, null).then(file => {
bannerId = file.id;
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 437c1fae3..af1b4d205 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -28,6 +28,8 @@
+
{{ i18n.ts.thisChannelArchived }}
+
@@ -36,6 +38,17 @@
+
+
+
+
+
+
+ {{ i18n.ts.search }}
+
+
+
+
@@ -63,8 +76,10 @@ import { deviceKind } from '@/scripts/device-kind';
import MkNotes from '@/components/MkNotes.vue';
import { url } from '@/config';
import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
import { defaultStore } from '@/store';
import MkNote from '@/components/MkNote.vue';
+import MkInfo from '@/components/MkInfo.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
const router = useRouter();
@@ -76,6 +91,8 @@ const props = defineProps<{
let tab = $ref('timeline');
let channel = $ref(null);
let favorited = $ref(false);
+let searchQuery = $ref('');
+let searchPagination = $ref();
const featuredPagination = $computed(() => ({
endpoint: 'notes/featured' as const,
limit: 10,
@@ -123,6 +140,21 @@ async function unfavorite() {
});
}
+async function search() {
+ const query = searchQuery.toString().trim();
+
+ if (query == null) return;
+
+ searchPagination = {
+ endpoint: 'notes/search',
+ limit: 10,
+ params: {
+ query: searchQuery,
+ channelId: channel.id,
+ },
+ };
+}
+
const headerActions = $computed(() => {
if (channel && channel.userId) {
const share = {
@@ -160,6 +192,10 @@ const headerTabs = $computed(() => [{
key: 'featured',
title: i18n.ts.featured,
icon: 'ti ti-bolt',
+}, {
+ key: 'search',
+ title: i18n.ts.search,
+ icon: 'ti ti-search',
}]);
definePageMetadata(computed(() => channel ? {
@@ -170,7 +206,7 @@ definePageMetadata(computed(() => channel ? {
diff --git a/packages/frontend/src/pages/settings/account-info.vue b/packages/frontend/src/pages/settings/account-stats.vue
similarity index 94%
rename from packages/frontend/src/pages/settings/account-info.vue
rename to packages/frontend/src/pages/settings/account-stats.vue
index 584808b0b..a0f1541b4 100644
--- a/packages/frontend/src/pages/settings/account-info.vue
+++ b/packages/frontend/src/pages/settings/account-stats.vue
@@ -1,18 +1,6 @@
-
- ID
- {{ $i.id }}
-
-
-
-
- {{ i18n.ts.registeredDate }}
-
-
-
-
-
+
{{ i18n.ts.statistics }}
{{ i18n.ts.notesCount }}
diff --git a/packages/frontend/src/pages/settings/delete-account.vue b/packages/frontend/src/pages/settings/delete-account.vue
deleted file mode 100644
index c6e79165c..000000000
--- a/packages/frontend/src/pages/settings/delete-account.vue
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
- {{ i18n.ts._accountDelete.mayTakeTime }}
- {{ i18n.ts._accountDelete.sendEmail }}
- {{ i18n.ts._accountDelete.requestAccountDelete }}
- {{ i18n.ts._accountDelete.inProgress }}
-
-
-
-
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index d3fb422e0..73c2b2e60 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -119,6 +119,13 @@ function saveProfile() {
os.api('i/update', {
alwaysMarkNsfw: !!alwaysMarkNsfw,
autoSensitive: !!autoSensitive,
+ }).catch(err => {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.error,
+ text: err.message,
+ });
+ alwaysMarkNsfw = true;
});
}
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 904fd3f95..ba0f3274f 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -20,29 +20,15 @@
- {{ i18n.ts.showFixedPostForm }}
- {{ i18n.ts.showFixedPostFormInChannel }}
-
- {{ i18n.ts.behavior }}
-
-
-
- {{ i18n.ts.openImageInNewTab }}
- {{ i18n.ts.enableInfiniteScroll }}
- {{ i18n.ts.useReactionPickerForContextMenu }}
-
-
- {{ i18n.ts.whenServerDisconnected }}
-
-
-
-
+
+ {{ i18n.ts.showFixedPostForm }}
+ {{ i18n.ts.showFixedPostFormInChannel }}
- {{ i18n.ts.appearance }}
+ {{ i18n.ts.displayOfNote }}
@@ -52,16 +38,70 @@
{{ i18n.ts.collapseRenotes }}
{{ i18n.ts.enableAdvancedMfm }}
{{ i18n.ts.enableAnimatedMfm }}
+ {{ i18n.ts.showGapBetweenNotesInTimeline }}
+ {{ i18n.ts.loadRawImages }}
+ {{ i18n.ts.useReactionPickerForContextMenu }}
+
+
+
+ {{ i18n.ts.instanceTicker }}
+
+
+
+
+
+
+ {{ i18n.ts.nsfw }}
+
+
+
+
+
+
+
+
+
+ {{ i18n.ts.notificationDisplay }}
+
+
+
+ {{ i18n.ts.position }}
+
+
+
+
+
+
+
+ {{ i18n.ts.stackAxis }}
+
+
+
+
+
+
+
+ {{ i18n.ts.appearance }}
+
+
+
{{ i18n.ts.reduceUiAnimation }}
{{ i18n.ts.useBlurEffect }}
{{ i18n.ts.useBlurEffectForModal }}
- {{ i18n.ts.showGapBetweenNotesInTimeline }}
- {{ i18n.ts.loadRawImages }}
{{ i18n.ts.disableShowingAnimatedImages }}
{{ i18n.ts.squareAvatars }}
{{ i18n.ts.useSystemFont }}
{{ i18n.ts.disableDrawer }}
{{ i18n.ts.forceShowAds }}
+ {{ i18n.ts.dataSaver }}
@@ -84,27 +124,29 @@
- {{ i18n.ts.aiChanMode }}
+ {{ i18n.ts.behavior }}
+
+
+
+ {{ i18n.ts.openImageInNewTab }}
+ {{ i18n.ts.enableInfiniteScroll }}
+
+
+ {{ i18n.ts.whenServerDisconnected }}
+
+
+
+
+
+ {{ i18n.ts.numberOfPageCache }}
+ {{ i18n.ts.numberOfPageCacheDescription }}
+
+
-
- {{ i18n.ts.instanceTicker }}
-
-
-
-
-
-
- {{ i18n.ts.nsfw }}
-
-
-
-
-
-
- {{ i18n.ts.numberOfPageCache }}
- {{ i18n.ts.numberOfPageCacheDescription }}
-
+
+ {{ i18n.ts.aiChanMode }}
+
{{ i18n.ts.deck }}
@@ -160,6 +202,7 @@ const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
+const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode'));
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
@@ -170,6 +213,9 @@ const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfin
const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
+const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
+const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
+const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string);
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
index a8274f560..89b410402 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -32,7 +32,7 @@
{{ i18n.ts.export }}
-
+
{{ i18n.ts.import }}
{{ i18n.ts.import }}
@@ -47,7 +47,7 @@
{{ i18n.ts.export }}
-
+
{{ i18n.ts.import }}
{{ i18n.ts.import }}
@@ -62,7 +62,7 @@
{{ i18n.ts.export }}
-
+
{{ i18n.ts.import }}
{{ i18n.ts.import }}
@@ -77,13 +77,28 @@
{{ i18n.ts.export }}
-
+
{{ i18n.ts.import }}
{{ i18n.ts.import }}
+
+ {{ i18n.ts.antennas }}
+
+
+ {{ i18n.ts.export }}
+
+ {{ i18n.ts.export }}
+
+
+ {{ i18n.ts.import }}
+
+ {{ i18n.ts.import }}
+
+
+
@@ -97,6 +112,7 @@ import * as os from '@/os';
import { selectFile } from '@/scripts/select-file';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
+import { $i } from '@/account';
const excludeMutingUsers = ref(false);
const excludeInactiveUsers = ref(false);
@@ -150,6 +166,10 @@ const exportMuting = () => {
os.api('i/export-mute', {}).then(onExportSuccess).catch(onError);
};
+const exportAntennas = () => {
+ os.api('i/export-antennas', {}).then(onExportSuccess).catch(onError);
+};
+
const importFollowing = async (ev) => {
const file = await selectFile(ev.currentTarget ?? ev.target);
os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError);
@@ -170,6 +190,11 @@ const importBlocking = async (ev) => {
os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
};
+const importAntennas = async (ev) => {
+ const file = await selectFile(ev.currentTarget ?? ev.target);
+ os.api('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
+
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 17af7417f..34a962ef4 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -164,12 +164,12 @@ const menuDef = computed(() => [{
text: i18n.ts.importAndExport,
to: '/settings/import-export',
active: currentPage?.route.name === 'import-export',
- }, /*{
+ }, {
icon: 'ti ti-plane',
- text: i18n.ts.accountMigration,
+ text: `${i18n.ts.accountMigration} (${i18n.ts.experimental})`,
to: '/settings/migration',
active: currentPage?.route.name === 'migration',
- },*/ {
+ }, {
icon: 'ti ti-dots',
text: i18n.ts.other,
to: '/settings/other',
diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue
index 2ef8af748..541992875 100644
--- a/packages/frontend/src/pages/settings/migration.vue
+++ b/packages/frontend/src/pages/settings/migration.vue
@@ -1,63 +1,121 @@
-
- {{ i18n.ts._accountMigration.moveTo }}
-
-
- {{ i18n.ts._accountMigration.moveToLabel }}
-
-
-
{{ i18n.ts._accountMigration.moveAccountDescription }}
-
-
+
+ {{ i18n.ts.thisIsExperimentalFeature }}
+
+
+
{{ i18n.ts._accountMigration.moveFrom }}
-
-
- {{ i18n.ts._accountMigration.moveFromLabel }}
-
-
-
{{ i18n.ts._accountMigration.moveFromDescription }}
+
{{ i18n.ts._accountMigration.moveFromSub }}
+
+
+
+ {{ i18n.ts._accountMigration.moveFromDescription }}
+
+
+ {{ i18n.ts.add }}
+ {{ i18n.ts.save }}
+
+
+
+
+ {{ i18n.t('_accountMigration.moveFromLabel', { n: i + 1 }) }}
+
+
+
+
+
+
+
+ {{ i18n.ts._accountMigration.moveTo }}
+
+
+
{{ i18n.ts._accountMigration.moveAccountDescription }}
+
+
+ {{ i18n.ts._accountMigration.moveAccountHowTo }}
+ {{ i18n.ts._accountMigration.moveCannotBeUndone }}
+
+
+
+ {{ i18n.ts._accountMigration.moveToLabel }}
+
+
+ {{ i18n.ts._accountMigration.startMigration }}
+
+
+
+ {{ i18n.ts._accountMigration.postMigrationNote }}
+ {{ i18n.ts._accountMigration.movedAndCannotBeUndone }}
+ {{ i18n.ts._accountMigration.movedTo }}
+
+
+
+
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 9f13f7a1d..1bf4cdc99 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -3,7 +3,7 @@
-
+
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 8c3478d8f..5bc157826 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -7,7 +7,7 @@
-
+
@@ -21,6 +21,9 @@
+
{{ i18n.ts.followsYou }}
@@ -45,6 +48,22 @@
{{ role.name }}
+
+
+ Moderation note
+
+
+
@@ -113,13 +132,14 @@
diff --git a/packages/frontend/src/pages/welcome.entrance.c.vue b/packages/frontend/src/pages/welcome.entrance.c.vue
deleted file mode 100644
index eca4e5764..000000000
--- a/packages/frontend/src/pages/welcome.entrance.c.vue
+++ /dev/null
@@ -1,308 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index 212d156a8..7728d97a6 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -1,8 +1,11 @@
-