summaryrefslogtreecommitdiff
path: root/packages/client/src/pages
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-01-27 00:17:13 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2022-01-27 00:17:13 +0900
commit5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff (patch)
tree51e9e6179f6d1bda3013d1412f6e43f9f8f70e86 /packages/client/src/pages
parentMerge branch 'develop' (diff)
parent12.102.0 (diff)
downloadmisskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.tar.gz
misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.tar.bz2
misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.zip
Merge branch 'develop'
Diffstat (limited to 'packages/client/src/pages')
-rw-r--r--packages/client/src/pages/_error_.vue89
-rw-r--r--packages/client/src/pages/_loading_.vue6
-rw-r--r--packages/client/src/pages/about-misskey.vue116
-rw-r--r--packages/client/src/pages/about.vue51
-rw-r--r--packages/client/src/pages/admin/abuses.vue88
-rw-r--r--packages/client/src/pages/admin/ads.vue10
-rw-r--r--packages/client/src/pages/admin/announcements.vue4
-rw-r--r--packages/client/src/pages/admin/bot-protection.vue105
-rw-r--r--packages/client/src/pages/admin/database.vue35
-rw-r--r--packages/client/src/pages/admin/email-settings.vue100
-rw-r--r--packages/client/src/pages/admin/emoji-edit-dialog.vue2
-rw-r--r--packages/client/src/pages/admin/emojis.vue361
-rw-r--r--packages/client/src/pages/admin/files-settings.vue93
-rw-r--r--packages/client/src/pages/admin/files.vue28
-rw-r--r--packages/client/src/pages/admin/index.vue34
-rw-r--r--packages/client/src/pages/admin/instance-block.vue30
-rw-r--r--packages/client/src/pages/admin/instance.vue291
-rw-r--r--packages/client/src/pages/admin/integrations.discord.vue (renamed from packages/client/src/pages/admin/integrations-discord.vue)40
-rw-r--r--packages/client/src/pages/admin/integrations.github.vue (renamed from packages/client/src/pages/admin/integrations-github.vue)40
-rw-r--r--packages/client/src/pages/admin/integrations.twitter.vue (renamed from packages/client/src/pages/admin/integrations-twitter.vue)40
-rw-r--r--packages/client/src/pages/admin/integrations.vue58
-rw-r--r--packages/client/src/pages/admin/metrics.vue6
-rw-r--r--packages/client/src/pages/admin/object-storage.vue126
-rw-r--r--packages/client/src/pages/admin/other-settings.vue54
-rw-r--r--packages/client/src/pages/admin/overview.vue14
-rw-r--r--packages/client/src/pages/admin/proxy-account.vue42
-rw-r--r--packages/client/src/pages/admin/queue.vue15
-rw-r--r--packages/client/src/pages/admin/relays.vue52
-rw-r--r--packages/client/src/pages/admin/security.vue73
-rw-r--r--packages/client/src/pages/admin/service-worker.vue85
-rw-r--r--packages/client/src/pages/admin/settings.vue226
-rw-r--r--packages/client/src/pages/admin/users.vue26
-rw-r--r--packages/client/src/pages/advanced-theme-editor.vue349
-rw-r--r--packages/client/src/pages/announcements.vue2
-rw-r--r--packages/client/src/pages/channel.vue6
-rw-r--r--packages/client/src/pages/channels.vue6
-rw-r--r--packages/client/src/pages/clip.vue6
-rw-r--r--packages/client/src/pages/drive.vue28
-rw-r--r--packages/client/src/pages/emojis.emoji.vue42
-rw-r--r--packages/client/src/pages/emojis.vue76
-rw-r--r--packages/client/src/pages/explore.vue4
-rw-r--r--packages/client/src/pages/favorites.vue74
-rw-r--r--packages/client/src/pages/featured.vue31
-rw-r--r--packages/client/src/pages/federation.vue97
-rw-r--r--packages/client/src/pages/follow-requests.vue88
-rw-r--r--packages/client/src/pages/gallery/edit.vue25
-rw-r--r--packages/client/src/pages/gallery/index.vue10
-rw-r--r--packages/client/src/pages/gallery/post.vue8
-rw-r--r--packages/client/src/pages/instance-info.vue298
-rw-r--r--packages/client/src/pages/mentions.vue29
-rw-r--r--packages/client/src/pages/messages.vue35
-rw-r--r--packages/client/src/pages/messaging/index.vue3
-rw-r--r--packages/client/src/pages/messaging/messaging-room.form.vue7
-rw-r--r--packages/client/src/pages/messaging/messaging-room.vue11
-rw-r--r--packages/client/src/pages/my-antennas/create.vue60
-rw-r--r--packages/client/src/pages/my-antennas/index.vue2
-rw-r--r--packages/client/src/pages/my-clips/index.vue103
-rw-r--r--packages/client/src/pages/my-groups/group.vue4
-rw-r--r--packages/client/src/pages/my-groups/index.vue6
-rw-r--r--packages/client/src/pages/my-lists/index.vue63
-rw-r--r--packages/client/src/pages/my-lists/list.vue4
-rw-r--r--packages/client/src/pages/not-found.vue18
-rw-r--r--packages/client/src/pages/note.vue14
-rw-r--r--packages/client/src/pages/notifications.vue106
-rw-r--r--packages/client/src/pages/page.vue8
-rw-r--r--packages/client/src/pages/pages.vue6
-rw-r--r--packages/client/src/pages/preview.vue24
-rw-r--r--packages/client/src/pages/reset-password.vue92
-rw-r--r--packages/client/src/pages/reversi/game.board.vue528
-rw-r--r--packages/client/src/pages/reversi/game.setting.vue390
-rw-r--r--packages/client/src/pages/reversi/game.vue76
-rw-r--r--packages/client/src/pages/reversi/index.vue279
-rw-r--r--packages/client/src/pages/room/preview.vue107
-rw-r--r--packages/client/src/pages/room/room.vue279
-rw-r--r--packages/client/src/pages/search.vue48
-rw-r--r--packages/client/src/pages/settings/2fa.vue3
-rw-r--r--packages/client/src/pages/settings/account-info.vue152
-rw-r--r--packages/client/src/pages/settings/accounts.vue36
-rw-r--r--packages/client/src/pages/settings/api.vue23
-rw-r--r--packages/client/src/pages/settings/apps.vue20
-rw-r--r--packages/client/src/pages/settings/custom-css.vue27
-rw-r--r--packages/client/src/pages/settings/deck.vue36
-rw-r--r--packages/client/src/pages/settings/delete-account.vue24
-rw-r--r--packages/client/src/pages/settings/drive.vue10
-rw-r--r--packages/client/src/pages/settings/email.vue6
-rw-r--r--packages/client/src/pages/settings/experimental-features.vue52
-rw-r--r--packages/client/src/pages/settings/general.vue4
-rw-r--r--packages/client/src/pages/settings/import-export.vue4
-rw-r--r--packages/client/src/pages/settings/index.vue30
-rw-r--r--packages/client/src/pages/settings/instance-mute.vue5
-rw-r--r--packages/client/src/pages/settings/integration.vue52
-rw-r--r--packages/client/src/pages/settings/menu.vue6
-rw-r--r--packages/client/src/pages/settings/mute-block.vue79
-rw-r--r--packages/client/src/pages/settings/notifications.vue6
-rw-r--r--packages/client/src/pages/settings/other.vue37
-rw-r--r--packages/client/src/pages/settings/plugin.install.vue36
-rw-r--r--packages/client/src/pages/settings/plugin.manage.vue116
-rw-r--r--packages/client/src/pages/settings/plugin.vue86
-rw-r--r--packages/client/src/pages/settings/privacy.vue90
-rw-r--r--packages/client/src/pages/settings/profile.vue349
-rw-r--r--packages/client/src/pages/settings/reaction.vue4
-rw-r--r--packages/client/src/pages/settings/registry.keys.vue114
-rw-r--r--packages/client/src/pages/settings/registry.value.vue147
-rw-r--r--packages/client/src/pages/settings/registry.vue90
-rw-r--r--packages/client/src/pages/settings/security.vue16
-rw-r--r--packages/client/src/pages/settings/sounds.vue6
-rw-r--r--packages/client/src/pages/settings/theme.install.vue146
-rw-r--r--packages/client/src/pages/settings/theme.manage.vue10
-rw-r--r--packages/client/src/pages/settings/theme.vue4
-rw-r--r--packages/client/src/pages/settings/update.vue95
-rw-r--r--packages/client/src/pages/settings/word-mute.vue6
-rw-r--r--packages/client/src/pages/share.vue2
-rw-r--r--packages/client/src/pages/signup-complete.vue56
-rw-r--r--packages/client/src/pages/tag.vue53
-rw-r--r--packages/client/src/pages/test.vue260
-rw-r--r--packages/client/src/pages/theme-editor.vue466
-rw-r--r--packages/client/src/pages/timeline.tutorial.vue24
-rw-r--r--packages/client/src/pages/timeline.vue258
-rw-r--r--packages/client/src/pages/user-ap-info.vue124
-rw-r--r--packages/client/src/pages/user-info.vue126
-rw-r--r--packages/client/src/pages/user/clips.vue2
-rw-r--r--packages/client/src/pages/user/follow-list.vue62
-rw-r--r--packages/client/src/pages/user/gallery.vue14
-rw-r--r--packages/client/src/pages/user/index.activity.vue27
-rw-r--r--packages/client/src/pages/user/index.timeline.vue60
-rw-r--r--packages/client/src/pages/user/index.vue876
-rw-r--r--packages/client/src/pages/user/pages.vue45
-rw-r--r--packages/client/src/pages/user/reactions.vue48
-rw-r--r--packages/client/src/pages/v.vue29
129 files changed, 2926 insertions, 7325 deletions
diff --git a/packages/client/src/pages/_error_.vue b/packages/client/src/pages/_error_.vue
index 2f8f08b5cf..7540995707 100644
--- a/packages/client/src/pages/_error_.vue
+++ b/packages/client/src/pages/_error_.vue
@@ -1,68 +1,61 @@
<template>
-<MkLoading v-if="!loaded" />
+<MkLoading v-if="!loaded"/>
<transition :name="$store.state.animation ? 'zoom' : ''" appear>
<div v-show="loaded" class="mjndxjch">
<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
- <p><b><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</b></p>
- <p v-if="version === meta.version">{{ $ts.pageLoadErrorDescription }}</p>
- <p v-else-if="serverIsDead">{{ $ts.serverIsDead }}</p>
+ <p><b><i class="fas fa-exclamation-triangle"></i> {{ i18n.locale.pageLoadError }}</b></p>
+ <p v-if="meta && (version === meta.version)">{{ i18n.locale.pageLoadErrorDescription }}</p>
+ <p v-else-if="serverIsDead">{{ i18n.locale.serverIsDead }}</p>
<template v-else>
- <p>{{ $ts.newVersionOfClientAvailable }}</p>
- <p>{{ $ts.youShouldUpgradeClient }}</p>
- <MkButton class="button primary" @click="reload">{{ $ts.reload }}</MkButton>
+ <p>{{ i18n.locale.newVersionOfClientAvailable }}</p>
+ <p>{{ i18n.locale.youShouldUpgradeClient }}</p>
+ <MkButton class="button primary" @click="reload">{{ i18n.locale.reload }}</MkButton>
</template>
- <p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p>
+ <p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.locale.troubleshooting }}</MkA></p>
<p v-if="error" class="error">ERROR: {{ error }}</p>
</div>
</transition>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
+import * as misskey from 'misskey-js';
import MkButton from '@/components/ui/button.vue';
import * as symbols from '@/symbols';
import { version } from '@/config';
import * as os from '@/os';
import { unisonReload } from '@/scripts/unison-reload';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkButton,
- },
- props: {
- error: {
- required: false,
- }
- },
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.error,
- icon: 'fas fa-exclamation-triangle'
- },
- loaded: false,
- serverIsDead: false,
- meta: {} as any,
- version,
- };
- },
- created() {
- os.api('meta', {
- detail: false
- }).then(meta => {
- this.loaded = true;
- this.serverIsDead = false;
- this.meta = meta;
- localStorage.setItem('v', meta.version);
- }, () => {
- this.loaded = true;
- this.serverIsDead = true;
- });
- },
- methods: {
- reload() {
- unisonReload();
- },
+const props = withDefaults(defineProps<{
+ error?: Error;
+}>(), {
+});
+
+let loaded = $ref(false);
+let serverIsDead = $ref(false);
+let meta = $ref<misskey.entities.LiteInstanceMetadata | null>(null);
+
+os.api('meta', {
+ detail: false,
+}).then(res => {
+ loaded = true;
+ serverIsDead = false;
+ meta = res;
+ localStorage.setItem('v', res.version);
+}, () => {
+ loaded = true;
+ serverIsDead = true;
+});
+
+function reload() {
+ unisonReload();
+}
+
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.error,
+ icon: 'fas fa-exclamation-triangle',
},
});
</script>
diff --git a/packages/client/src/pages/_loading_.vue b/packages/client/src/pages/_loading_.vue
index 05c6af1cd7..1dd2e46e10 100644
--- a/packages/client/src/pages/_loading_.vue
+++ b/packages/client/src/pages/_loading_.vue
@@ -2,9 +2,5 @@
<MkLoading/>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@/os';
-
-export default defineComponent({});
+<script lang="ts" setup>
</script>
diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue
index 855a21e493..8119f33051 100644
--- a/packages/client/src/pages/about-misskey.vue
+++ b/packages/client/src/pages/about-misskey.vue
@@ -3,36 +3,39 @@
<MkSpacer :content-max="600" :margin-min="20">
<div class="_formRoot znqjceqz">
<div id="debug"></div>
- <div ref="about" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }">
+ <div ref="containerEl" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }">
<img src="/client-assets/about-icon.png" alt="" class="icon" draggable="false" @load="iconLoaded" @click="gravity"/>
<div class="misskey">Misskey</div>
<div class="version">v{{ version }}</div>
<span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span>
</div>
<div class="_formBlock" style="text-align: center;">
- {{ $ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ $ts.learnMore }}</a>
+ {{ i18n.locale._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.locale.learnMore }}</a>
+ </div>
+ <div class="_formBlock" style="text-align: center;">
+ <MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton>
</div>
<FormSection>
<div class="_formLinks">
<FormLink to="https://github.com/misskey-dev/misskey" external>
<template #icon><i class="fas fa-code"></i></template>
- {{ $ts._aboutMisskey.source }}
+ {{ i18n.locale._aboutMisskey.source }}
<template #suffix>GitHub</template>
</FormLink>
<FormLink to="https://crowdin.com/project/misskey" external>
<template #icon><i class="fas fa-language"></i></template>
- {{ $ts._aboutMisskey.translation }}
+ {{ i18n.locale._aboutMisskey.translation }}
<template #suffix>Crowdin</template>
</FormLink>
<FormLink to="https://www.patreon.com/syuilo" external>
<template #icon><i class="fas fa-hand-holding-medical"></i></template>
- {{ $ts._aboutMisskey.donate }}
+ {{ i18n.locale._aboutMisskey.donate }}
<template #suffix>Patreon</template>
</FormLink>
</div>
</FormSection>
<FormSection>
- <template #label>{{ $ts._aboutMisskey.contributors }}</template>
+ <template #label>{{ i18n.locale._aboutMisskey.contributors }}</template>
<div class="_formLinks">
<FormLink to="https://github.com/syuilo" external>@syuilo</FormLink>
<FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink>
@@ -44,27 +47,30 @@
<FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink>
<FormLink to="https://github.com/marihachi" external>@marihachi</FormLink>
</div>
- <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ $ts._aboutMisskey.allContributors }}</MkLink></template>
+ <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ i18n.locale._aboutMisskey.allContributors }}</MkLink></template>
</FormSection>
<FormSection>
- <template #label><Mfm text="$[jelly ❤]"/> {{ $ts._aboutMisskey.patrons }}</template>
+ <template #label><Mfm text="$[jelly ❤]"/> {{ i18n.locale._aboutMisskey.patrons }}</template>
<div v-for="patron in patrons" :key="patron">{{ patron }}</div>
- <template #caption>{{ $ts._aboutMisskey.morePatrons }}</template>
+ <template #caption>{{ i18n.locale._aboutMisskey.morePatrons }}</template>
</FormSection>
</div>
</MkSpacer>
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { nextTick, onBeforeUnmount } from 'vue';
import { version } from '@/config';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
-import MkKeyValue from '@/components/key-value.vue';
+import MkButton from '@/components/ui/button.vue';
import MkLink from '@/components/link.vue';
import { physics } from '@/scripts/physics';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
+import { defaultStore } from '@/store';
+import * as os from '@/os';
const patrons = [
'まっちゃとーにゅ',
@@ -145,59 +151,53 @@ const patrons = [
'蝉暮せせせ',
];
-export default defineComponent({
- components: {
- FormSection,
- FormLink,
- MkKeyValue,
- MkLink,
- },
+let easterEggReady = false;
+let easterEggEmojis = $ref([]);
+let easterEggEngine = $ref(null);
+const containerEl = $ref<HTMLElement>();
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.aboutMisskey,
- icon: null
- },
- version,
- patrons,
- easterEggReady: false,
- easterEggEmojis: [],
- easterEggEngine: null,
- }
- },
+function iconLoaded() {
+ const emojis = defaultStore.state.reactions;
+ const containerWidth = containerEl.offsetWidth;
+ for (let i = 0; i < 32; i++) {
+ easterEggEmojis.push({
+ id: i.toString(),
+ top: -(128 + (Math.random() * 256)),
+ left: (Math.random() * containerWidth),
+ emoji: emojis[Math.floor(Math.random() * emojis.length)],
+ });
+ }
- beforeUnmount() {
- if (this.easterEggEngine) {
- this.easterEggEngine.stop();
- }
- },
+ nextTick(() => {
+ easterEggReady = true;
+ });
+}
- methods: {
- iconLoaded() {
- const emojis = this.$store.state.reactions;
- const containerWidth = this.$refs.about.offsetWidth;
- for (let i = 0; i < 32; i++) {
- this.easterEggEmojis.push({
- id: i.toString(),
- top: -(128 + (Math.random() * 256)),
- left: (Math.random() * containerWidth),
- emoji: emojis[Math.floor(Math.random() * emojis.length)],
- });
- }
+function gravity() {
+ if (!easterEggReady) return;
+ easterEggReady = false;
+ easterEggEngine = physics(containerEl);
+}
- this.$nextTick(() => {
- this.easterEggReady = true;
- });
- },
+function iLoveMisskey() {
+ os.post({
+ initialText: 'I $[jelly ❤] #Misskey',
+ });
+}
- gravity() {
- if (!this.easterEggReady) return;
- this.easterEggReady = false;
- this.easterEggEngine = physics(this.$refs.about);
- }
+onBeforeUnmount(() => {
+ if (easterEggEngine) {
+ easterEggEngine.stop();
}
});
+
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.aboutMisskey,
+ icon: null,
+ bg: 'var(--bg)',
+ },
+});
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/pages/about.vue b/packages/client/src/pages/about.vue
index 04f68b7201..a5984c548d 100644
--- a/packages/client/src/pages/about.vue
+++ b/packages/client/src/pages/about.vue
@@ -24,7 +24,7 @@
</FormSection>
<FormSection>
- <div class="_inputSplit _formBlock">
+ <FormSplit>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.administrator }}</template>
<template #value>{{ $instance.maintainerName }}</template>
@@ -33,14 +33,14 @@
<template #key>{{ $ts.contact }}</template>
<template #value>{{ $instance.maintainerEmail }}</template>
</MkKeyValue>
- </div>
+ </FormSplit>
<FormLink v-if="$instance.tosUrl" :to="$instance.tosUrl" class="_formBlock" external>{{ $ts.tos }}</FormLink>
</FormSection>
<FormSuspense :p="initStats">
<FormSection>
<template #label>{{ $ts.statistics }}</template>
- <div class="_inputSplit">
+ <FormSplit>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.users }}</template>
<template #value>{{ number(stats.originalUsersCount) }}</template>
@@ -49,7 +49,7 @@
<template #key>{{ $ts.notes }}</template>
<template #value>{{ number(stats.originalNotesCount) }}</template>
</MkKeyValue>
- </div>
+ </FormSplit>
</FormSection>
</FormSuspense>
@@ -67,46 +67,33 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
import { version, instanceName } from '@/config';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import FormSuspense from '@/components/form/suspense.vue';
+import FormSplit from '@/components/form/split.vue';
import MkKeyValue from '@/components/key-value.vue';
import * as os from '@/os';
import number from '@/filters/number';
import * as symbols from '@/symbols';
import { host } from '@/config';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkKeyValue,
- FormSection,
- FormLink,
- FormSuspense,
- },
+const stats = ref(null);
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.instanceInfo,
- icon: 'fas fa-info-circle'
- },
- host,
- version,
- instanceName,
- stats: null,
- initStats: () => os.api('stats', {
- }).then((stats) => {
- this.stats = stats;
- })
- }
- },
+const initStats = () => os.api('stats', {
+}).then((res) => {
+ stats.value = res;
+});
- methods: {
- number
- }
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.instanceInfo,
+ icon: 'fas fa-info-circle',
+ bg: 'var(--bg)',
+ },
});
</script>
diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue
index 8df20097b3..92f93797ce 100644
--- a/packages/client/src/pages/admin/abuses.vue
+++ b/packages/client/src/pages/admin/abuses.vue
@@ -34,27 +34,7 @@
-->
<MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);">
- <div v-for="report in items" :key="report.id" class="bcekxzvu _card _gap">
- <div class="_content target">
- <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/>
- <div class="info">
- <MkUserName class="name" :user="report.targetUser"/>
- <div class="acct">@{{ acct(report.targetUser) }}</div>
- </div>
- </div>
- <div class="_content">
- <div>
- <Mfm :text="report.comment"/>
- </div>
- <hr>
- <div>Reporter: <MkAcct :user="report.reporter"/></div>
- <div><MkTime :time="report.createdAt"/></div>
- </div>
- <div class="_footer">
- <div v-if="report.assignee">Assignee: <MkAcct :user="report.assignee"/></div>
- <MkButton v-if="!report.resolved" primary @click="resolve(report)">{{ $ts.abuseMarkAsResolved }}</MkButton>
- </div>
- </div>
+ <XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/>
</MkPagination>
</div>
</div>
@@ -62,22 +42,21 @@
</template>
<script lang="ts">
-import { defineComponent } from 'vue';
+import { computed, defineComponent } from 'vue';
-import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
import MkPagination from '@/components/ui/pagination.vue';
-import { acct } from '@/filters/user';
+import XAbuseReport from '@/components/abuse-report.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
- MkButton,
MkInput,
MkSelect,
MkPagination,
+ XAbuseReport,
},
emits: ['info'],
@@ -95,44 +74,20 @@ export default defineComponent({
reporterOrigin: 'combined',
targetUserOrigin: 'combined',
pagination: {
- endpoint: 'admin/abuse-user-reports',
+ endpoint: 'admin/abuse-user-reports' as const,
limit: 10,
- params: () => ({
+ params: computed(() => ({
state: this.state,
reporterOrigin: this.reporterOrigin,
targetUserOrigin: this.targetUserOrigin,
- }),
+ })),
},
}
},
- watch: {
- state() {
- this.$refs.reports.reload();
- },
-
- reporterOrigin() {
- this.$refs.reports.reload();
- },
-
- targetUserOrigin() {
- this.$refs.reports.reload();
- },
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
- acct,
-
- resolve(report) {
- os.apiWithDialog('admin/resolve-abuse-user-report', {
- reportId: report.id,
- }).then(() => {
- this.$refs.reports.removeItem(item => item.id === report.id);
- });
+ resolved(reportId) {
+ this.$refs.reports.removeItem(item => item.id === reportId);
},
}
});
@@ -142,29 +97,4 @@ export default defineComponent({
.lcixvhis {
margin: var(--margin);
}
-
-.bcekxzvu {
- > .target {
- display: flex;
- width: 100%;
- box-sizing: border-box;
- text-align: left;
- align-items: center;
-
- > .avatar {
- width: 42px;
- height: 42px;
- }
-
- > .info {
- margin-left: 0.3em;
- padding: 0 8px;
- flex: 1;
-
- > .name {
- font-weight: bold;
- }
- }
- }
-}
</style>
diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue
index d12ed8563e..8f164caa99 100644
--- a/packages/client/src/pages/admin/ads.vue
+++ b/packages/client/src/pages/admin/ads.vue
@@ -23,14 +23,14 @@
<MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio>
</div>
-->
- <div class="_inputSplit">
+ <FormSplit>
<MkInput v-model="ad.ratio" type="number">
<template #label>{{ $ts.ratio }}</template>
</MkInput>
<MkInput v-model="ad.expiresAt" type="date">
<template #label>{{ $ts.expiration }}</template>
</MkInput>
- </div>
+ </FormSplit>
<MkTextarea v-model="ad.memo" class="_formBlock">
<template #label>{{ $ts.memo }}</template>
</MkTextarea>
@@ -49,6 +49,7 @@ import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkTextarea from '@/components/form/textarea.vue';
import FormRadios from '@/components/form/radios.vue';
+import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
@@ -58,6 +59,7 @@ export default defineComponent({
MkInput,
MkTextarea,
FormRadios,
+ FormSplit,
},
emits: ['info'],
@@ -85,10 +87,6 @@ export default defineComponent({
});
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
add() {
this.ads.unshift({
diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue
index 3614cb1441..a0d720bb29 100644
--- a/packages/client/src/pages/admin/announcements.vue
+++ b/packages/client/src/pages/admin/announcements.vue
@@ -61,10 +61,6 @@ export default defineComponent({
});
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
add() {
this.announcements.unshift({
diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue
index 5a97083841..82ab155317 100644
--- a/packages/client/src/pages/admin/bot-protection.vue
+++ b/packages/client/src/pages/admin/bot-protection.vue
@@ -1,70 +1,55 @@
<template>
-<FormBase>
+<div>
<FormSuspense :p="init">
- <FormRadios v-model="provider">
- <template #desc><i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}</template>
- <option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option>
- <option value="hcaptcha">hCaptcha</option>
- <option value="recaptcha">reCAPTCHA</option>
- </FormRadios>
+ <div class="_formRoot">
+ <FormRadios v-model="provider" class="_formBlock">
+ <option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option>
+ <option value="hcaptcha">hCaptcha</option>
+ <option value="recaptcha">reCAPTCHA</option>
+ </FormRadios>
- <template v-if="provider === 'hcaptcha'">
- <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
- <div class="_debobigegoLabel">hCaptcha</div>
- <div class="main">
- <FormInput v-model="hcaptchaSiteKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>{{ $ts.hcaptchaSiteKey }}</span>
- </FormInput>
- <FormInput v-model="hcaptchaSecretKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>{{ $ts.hcaptchaSecretKey }}</span>
- </FormInput>
- </div>
- </div>
- <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
- <div class="_debobigegoLabel">{{ $ts.preview }}</div>
- <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);">
+ <template v-if="provider === 'hcaptcha'">
+ <FormInput v-model="hcaptchaSiteKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>{{ $ts.hcaptchaSiteKey }}</template>
+ </FormInput>
+ <FormInput v-model="hcaptchaSecretKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>{{ $ts.hcaptchaSecretKey }}</template>
+ </FormInput>
+ <FormSlot class="_formBlock">
+ <template #label>{{ $ts.preview }}</template>
<MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
- </div>
- </div>
- </template>
- <template v-else-if="provider === 'recaptcha'">
- <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
- <div class="_debobigegoLabel">reCAPTCHA</div>
- <div class="main">
- <FormInput v-model="recaptchaSiteKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>{{ $ts.recaptchaSiteKey }}</span>
- </FormInput>
- <FormInput v-model="recaptchaSecretKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>{{ $ts.recaptchaSecretKey }}</span>
- </FormInput>
- </div>
- </div>
- <div v-if="recaptchaSiteKey" v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
- <div class="_debobigegoLabel">{{ $ts.preview }}</div>
- <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);">
+ </FormSlot>
+ </template>
+ <template v-else-if="provider === 'recaptcha'">
+ <FormInput v-model="recaptchaSiteKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>{{ $ts.recaptchaSiteKey }}</template>
+ </FormInput>
+ <FormInput v-model="recaptchaSecretKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>{{ $ts.recaptchaSecretKey }}</template>
+ </FormInput>
+ <FormSlot v-if="recaptchaSiteKey" class="_formBlock">
+ <template #label>{{ $ts.preview }}</template>
<MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/>
- </div>
- </div>
- </template>
+ </FormSlot>
+ </template>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ </div>
</FormSuspense>
-</FormBase>
+</div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
-import FormRadios from '@/components/debobigego/radios.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormRadios from '@/components/form/radios.vue';
+import FormInput from '@/components/form/input.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormSuspense from '@/components/form/suspense.vue';
+import FormSlot from '@/components/form/slot.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@@ -73,11 +58,9 @@ export default defineComponent({
components: {
FormRadios,
FormInput,
- FormBase,
- FormGroup,
FormButton,
- FormInfo,
FormSuspense,
+ FormSlot,
MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')),
},
@@ -99,10 +82,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue
index b09f1ad867..3a835eeafa 100644
--- a/packages/client/src/pages/admin/database.vue
+++ b/packages/client/src/pages/admin/database.vue
@@ -1,28 +1,18 @@
<template>
-<FormBase>
+<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
<FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
- <FormGroup v-for="table in database" :key="table[0]">
- <template #label>{{ table[0] }}</template>
- <FormKeyValueView>
- <template #key>Size</template>
- <template #value>{{ bytes(table[1].size) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>Records</template>
- <template #value>{{ number(table[1].count) }}</template>
- </FormKeyValueView>
- </FormGroup>
+ <MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;">
+ <template #key>{{ table[0] }}</template>
+ <template #value>{{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs)</template>
+ </MkKeyValue>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
+import FormSuspense from '@/components/form/suspense.vue';
+import MkKeyValue from '@/components/key-value.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import bytes from '@/filters/bytes';
@@ -31,10 +21,7 @@ import number from '@/filters/number';
export default defineComponent({
components: {
FormSuspense,
- FormKeyValueView,
- FormBase,
- FormGroup,
- FormLink,
+ MkKeyValue,
},
emits: ['info'],
@@ -50,10 +37,6 @@ export default defineComponent({
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
bytes, number,
}
diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue
index 873a853918..6491a453ab 100644
--- a/packages/client/src/pages/admin/email-settings.vue
+++ b/packages/client/src/pages/admin/email-settings.vue
@@ -1,50 +1,55 @@
<template>
-<FormBase>
+<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <FormSwitch v-model="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch>
+ <div class="_formRoot">
+ <FormSwitch v-model="enableEmail" class="_formBlock">
+ <template #label>{{ $ts.enableEmail }}</template>
+ <template #caption>{{ $ts.emailConfigInfo }}</template>
+ </FormSwitch>
- <template v-if="enableEmail">
- <FormInput v-model="email" type="email">
- <span>{{ $ts.emailAddress }}</span>
- </FormInput>
+ <template v-if="enableEmail">
+ <FormInput v-model="email" type="email" class="_formBlock">
+ <template #label>{{ $ts.emailAddress }}</template>
+ </FormInput>
- <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
- <div class="_debobigegoLabel">{{ $ts.smtpConfig }}</div>
- <div class="main">
- <FormInput v-model="smtpHost">
- <span>{{ $ts.smtpHost }}</span>
- </FormInput>
- <FormInput v-model="smtpPort" type="number">
- <span>{{ $ts.smtpPort }}</span>
- </FormInput>
- <FormInput v-model="smtpUser">
- <span>{{ $ts.smtpUser }}</span>
- </FormInput>
- <FormInput v-model="smtpPass" type="password">
- <span>{{ $ts.smtpPass }}</span>
- </FormInput>
- <FormInfo>{{ $ts.emptyToDisableSmtpAuth }}</FormInfo>
- <FormSwitch v-model="smtpSecure">{{ $ts.smtpSecure }}<template #desc>{{ $ts.smtpSecureInfo }}</template></FormSwitch>
- </div>
- </div>
-
- <FormButton @click="testEmail">{{ $ts.testEmail }}</FormButton>
- </template>
-
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ <FormSection>
+ <template #label>{{ $ts.smtpConfig }}</template>
+ <FormSplit :min-width="280">
+ <FormInput v-model="smtpHost" class="_formBlock">
+ <template #label>{{ $ts.smtpHost }}</template>
+ </FormInput>
+ <FormInput v-model="smtpPort" type="number" class="_formBlock">
+ <template #label>{{ $ts.smtpPort }}</template>
+ </FormInput>
+ </FormSplit>
+ <FormSplit :min-width="280">
+ <FormInput v-model="smtpUser" class="_formBlock">
+ <template #label>{{ $ts.smtpUser }}</template>
+ </FormInput>
+ <FormInput v-model="smtpPass" type="password" class="_formBlock">
+ <template #label>{{ $ts.smtpPass }}</template>
+ </FormInput>
+ </FormSplit>
+ <FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo>
+ <FormSwitch v-model="smtpSecure" class="_formBlock">
+ <template #label>{{ $ts.smtpSecure }}</template>
+ <template #caption>{{ $ts.smtpSecureInfo }}</template>
+ </FormSwitch>
+ </FormSection>
+ </template>
+ </div>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormInput from '@/components/form/input.vue';
+import FormInfo from '@/components/ui/info.vue';
+import FormSuspense from '@/components/form/suspense.vue';
+import FormSplit from '@/components/form/split.vue';
+import FormSection from '@/components/form/section.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@@ -53,9 +58,8 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
- FormBase,
- FormGroup,
- FormButton,
+ FormSplit,
+ FormSection,
FormInfo,
FormSuspense,
},
@@ -68,6 +72,16 @@ export default defineComponent({
title: this.$ts.emailServer,
icon: 'fas fa-envelope',
bg: 'var(--bg)',
+ actions: [{
+ asFullButton: true,
+ text: this.$ts.testEmail,
+ handler: this.testEmail,
+ }, {
+ asFullButton: true,
+ icon: 'fas fa-check',
+ text: this.$ts.save,
+ handler: this.save,
+ }],
},
enableEmail: false,
email: null,
@@ -79,10 +93,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue
index a45d92fa16..2e3903426e 100644
--- a/packages/client/src/pages/admin/emoji-edit-dialog.vue
+++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue
@@ -95,7 +95,7 @@ export default defineComponent({
});
if (canceled) return;
- os.api('admin/emoji/remove', {
+ os.api('admin/emoji/delete', {
id: this.emoji.id
}).then(() => {
this.$emit('done', {
diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue
index 49277325a0..5b1dfe565a 100644
--- a/packages/client/src/pages/admin/emojis.vue
+++ b/packages/client/src/pages/admin/emojis.vue
@@ -6,11 +6,22 @@
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.search }}</template>
</MkInput>
- <MkPagination ref="emojis" :pagination="pagination">
+ <MkSwitch v-model="selectMode" style="margin: 8px 0;">
+ <template #label>Select mode</template>
+ </MkSwitch>
+ <div v-if="selectMode" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+ <MkButton inline @click="selectAll">Select all</MkButton>
+ <MkButton inline @click="setCategoryBulk">Set category</MkButton>
+ <MkButton inline @click="addTagBulk">Add tag</MkButton>
+ <MkButton inline @click="removeTagBulk">Remove tag</MkButton>
+ <MkButton inline @click="setTagBulk">Set tag</MkButton>
+ <MkButton inline danger @click="delBulk">Delete</MkButton>
+ </div>
+ <MkPagination ref="emojisPaginationComponent" :pagination="pagination">
<template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
<template v-slot="{items}">
<div class="ldhfsamy">
- <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)">
+ <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)">
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<div class="name _monospace">{{ emoji.name }}</div>
@@ -23,7 +34,7 @@
</div>
<div v-else-if="tab === 'remote'" class="remote">
- <div class="_inputSplit">
+ <FormSplit>
<MkInput v-model="queryRemote" :debounce="true" type="search">
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.search }}</template>
@@ -31,8 +42,8 @@
<MkInput v-model="host" :debounce="true">
<template #label>{{ $ts.host }}</template>
</MkInput>
- </div>
- <MkPagination ref="remoteEmojis" :pagination="remotePagination">
+ </FormSplit>
+ <MkPagination :pagination="remotePagination">
<template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
<template v-slot="{items}">
<div class="ldhfsamy">
@@ -51,146 +62,233 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { computed, defineComponent, toRef } from 'vue';
+<script lang="ts" setup>
+import { computed, defineComponent, ref, toRef } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkPagination from '@/components/ui/pagination.vue';
import MkTab from '@/components/tab.vue';
-import { selectFiles } from '@/scripts/select-file';
+import MkSwitch from '@/components/form/switch.vue';
+import FormSplit from '@/components/form/split.vue';
+import { selectFile, selectFiles } from '@/scripts/select-file';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkTab,
- MkButton,
- MkInput,
- MkPagination,
- },
+const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>();
- emits: ['info'],
+const tab = ref('local');
+const query = ref(null);
+const queryRemote = ref(null);
+const host = ref(null);
+const selectMode = ref(false);
+const selectedEmojis = ref<string[]>([]);
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => ({
- title: this.$ts.customEmojis,
- icon: 'fas fa-laugh',
- bg: 'var(--bg)',
- actions: [{
- asFullButton: true,
- icon: 'fas fa-plus',
- text: this.$ts.addEmoji,
- handler: this.add,
- }, {
- icon: 'fas fa-ellipsis-h',
- handler: this.menu,
- }],
- tabs: [{
- active: this.tab === 'local',
- title: this.$ts.local,
- onClick: () => { this.tab = 'local'; },
- }, {
- active: this.tab === 'remote',
- title: this.$ts.remote,
- onClick: () => { this.tab = 'remote'; },
- },]
- })),
- tab: 'local',
- query: null,
- queryRemote: null,
- host: '',
- pagination: {
- endpoint: 'admin/emoji/list',
- limit: 30,
- params: computed(() => ({
- query: (this.query && this.query !== '') ? this.query : null
- }))
- },
- remotePagination: {
- endpoint: 'admin/emoji/list-remote',
- limit: 30,
- params: computed(() => ({
- query: (this.queryRemote && this.queryRemote !== '') ? this.queryRemote : null,
- host: (this.host && this.host !== '') ? this.host : null
- }))
- },
- }
- },
+const pagination = {
+ endpoint: 'admin/emoji/list' as const,
+ limit: 30,
+ params: computed(() => ({
+ query: (query.value && query.value !== '') ? query.value : null,
+ })),
+};
- async mounted() {
- this.$emit('info', toRef(this, symbols.PAGE_INFO));
- },
+const remotePagination = {
+ endpoint: 'admin/emoji/list-remote' as const,
+ limit: 30,
+ params: computed(() => ({
+ query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null,
+ host: (host.value && host.value !== '') ? host.value : null,
+ })),
+};
- methods: {
- async add(e) {
- const files = await selectFiles(e.currentTarget || e.target, null);
+const selectAll = () => {
+ if (selectedEmojis.value.length > 0) {
+ selectedEmojis.value = [];
+ } else {
+ selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id);
+ }
+};
- const promise = Promise.all(files.map(file => os.api('admin/emoji/add', {
- fileId: file.id,
- })));
- promise.then(() => {
- this.$refs.emojis.reload();
- });
- os.promiseDialog(promise);
- },
+const toggleSelect = (emoji) => {
+ if (selectedEmojis.value.includes(emoji.id)) {
+ selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id);
+ } else {
+ selectedEmojis.value.push(emoji.id);
+ }
+};
- edit(emoji) {
- os.popup(import('./emoji-edit-dialog.vue'), {
- emoji: emoji
- }, {
- done: result => {
- if (result.updated) {
- this.$refs.emojis.replaceItem(item => item.id === emoji.id, {
- ...emoji,
- ...result.updated
- });
- } else if (result.deleted) {
- this.$refs.emojis.removeItem(item => item.id === emoji.id);
- }
- },
- }, 'closed');
- },
+const add = async (ev: MouseEvent) => {
+ const files = await selectFiles(ev.currentTarget || ev.target, null);
- im(emoji) {
- os.apiWithDialog('admin/emoji/copy', {
- emojiId: emoji.id,
- });
- },
+ const promise = Promise.all(files.map(file => os.api('admin/emoji/add', {
+ fileId: file.id,
+ })));
+ promise.then(() => {
+ emojisPaginationComponent.value.reload();
+ });
+ os.promiseDialog(promise);
+};
- remoteMenu(emoji, ev) {
- os.popupMenu([{
- type: 'label',
- text: ':' + emoji.name + ':',
- }, {
- text: this.$ts.import,
- icon: 'fas fa-plus',
- action: () => { this.im(emoji) }
- }], ev.currentTarget || ev.target);
+const edit = (emoji) => {
+ os.popup(import('./emoji-edit-dialog.vue'), {
+ emoji: emoji
+ }, {
+ done: result => {
+ if (result.updated) {
+ emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, {
+ ...emoji,
+ ...result.updated
+ });
+ } else if (result.deleted) {
+ emojisPaginationComponent.value.removeItem(item => item.id === emoji.id);
+ }
},
+ }, 'closed');
+};
- menu(ev) {
- os.popupMenu([{
- icon: 'fas fa-download',
- text: this.$ts.export,
- action: async () => {
- os.api('export-custom-emojis', {
- })
- .then(() => {
- os.alert({
- type: 'info',
- text: this.$ts.exportRequested,
- });
- }).catch((e) => {
- os.alert({
- type: 'error',
- text: e.message,
- });
- });
- }
- }], ev.currentTarget || ev.target);
+const im = (emoji) => {
+ os.apiWithDialog('admin/emoji/copy', {
+ emojiId: emoji.id,
+ });
+};
+
+const remoteMenu = (emoji, ev: MouseEvent) => {
+ os.popupMenu([{
+ type: 'label',
+ text: ':' + emoji.name + ':',
+ }, {
+ text: i18n.locale.import,
+ icon: 'fas fa-plus',
+ action: () => { im(emoji) }
+ }], ev.currentTarget || ev.target);
+};
+
+const menu = (ev: MouseEvent) => {
+ os.popupMenu([{
+ icon: 'fas fa-download',
+ text: i18n.locale.export,
+ action: async () => {
+ os.api('export-custom-emojis', {
+ })
+ .then(() => {
+ os.alert({
+ type: 'info',
+ text: i18n.locale.exportRequested,
+ });
+ }).catch((e) => {
+ os.alert({
+ type: 'error',
+ text: e.message,
+ });
+ });
}
- }
+ }, {
+ icon: 'fas fa-upload',
+ text: i18n.locale.import,
+ action: async () => {
+ const file = await selectFile(ev.currentTarget || ev.target);
+ os.api('admin/emoji/import-zip', {
+ fileId: file.id,
+ })
+ .then(() => {
+ os.alert({
+ type: 'info',
+ text: i18n.locale.importRequested,
+ });
+ }).catch((e) => {
+ os.alert({
+ type: 'error',
+ text: e.message,
+ });
+ });
+ }
+ }], ev.currentTarget || ev.target);
+};
+
+const setCategoryBulk = async () => {
+ const { canceled, result } = await os.inputText({
+ title: 'Category',
+ });
+ if (canceled) return;
+ await os.apiWithDialog('admin/emoji/set-category-bulk', {
+ ids: selectedEmojis.value,
+ category: result,
+ });
+ emojisPaginationComponent.value.reload();
+};
+
+const addTagBulk = async () => {
+ const { canceled, result } = await os.inputText({
+ title: 'Tag',
+ });
+ if (canceled) return;
+ await os.apiWithDialog('admin/emoji/add-aliases-bulk', {
+ ids: selectedEmojis.value,
+ aliases: result.split(' '),
+ });
+ emojisPaginationComponent.value.reload();
+};
+
+const removeTagBulk = async () => {
+ const { canceled, result } = await os.inputText({
+ title: 'Tag',
+ });
+ if (canceled) return;
+ await os.apiWithDialog('admin/emoji/remove-aliases-bulk', {
+ ids: selectedEmojis.value,
+ aliases: result.split(' '),
+ });
+ emojisPaginationComponent.value.reload();
+};
+
+const setTagBulk = async () => {
+ const { canceled, result } = await os.inputText({
+ title: 'Tag',
+ });
+ if (canceled) return;
+ await os.apiWithDialog('admin/emoji/set-aliases-bulk', {
+ ids: selectedEmojis.value,
+ aliases: result.split(' '),
+ });
+ emojisPaginationComponent.value.reload();
+};
+
+const delBulk = async () => {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ text: i18n.locale.deleteConfirm,
+ });
+ if (canceled) return;
+ await os.apiWithDialog('admin/emoji/delete-bulk', {
+ ids: selectedEmojis.value,
+ });
+ emojisPaginationComponent.value.reload();
+};
+
+defineExpose({
+ [symbols.PAGE_INFO]: computed(() => ({
+ title: i18n.locale.customEmojis,
+ icon: 'fas fa-laugh',
+ bg: 'var(--bg)',
+ actions: [{
+ asFullButton: true,
+ icon: 'fas fa-plus',
+ text: i18n.locale.addEmoji,
+ handler: add,
+ }, {
+ icon: 'fas fa-ellipsis-h',
+ handler: menu,
+ }],
+ tabs: [{
+ active: tab.value === 'local',
+ title: i18n.locale.local,
+ onClick: () => { tab.value = 'local'; },
+ }, {
+ active: tab.value === 'remote',
+ title: i18n.locale.remote,
+ onClick: () => { tab.value = 'remote'; },
+ },]
+ })),
});
</script>
@@ -210,11 +308,16 @@ export default defineComponent({
> .emoji {
display: flex;
align-items: center;
- padding: 12px;
+ padding: 11px;
text-align: left;
+ border: solid 1px var(--panel);
&:hover {
- color: var(--accent);
+ border-color: var(--inputBorderHover);
+ }
+
+ &.selected {
+ border-color: var(--accent);
}
> .img {
diff --git a/packages/client/src/pages/admin/files-settings.vue b/packages/client/src/pages/admin/files-settings.vue
deleted file mode 100644
index df25bd0fb2..0000000000
--- a/packages/client/src/pages/admin/files-settings.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="cacheRemoteFiles">
- {{ $ts.cacheRemoteFiles }}
- <template #desc>{{ $ts.cacheRemoteFilesDescription }}</template>
- </FormSwitch>
-
- <FormSwitch v-model="proxyRemoteFiles">
- {{ $ts.proxyRemoteFiles }}
- <template #desc>{{ $ts.proxyRemoteFilesDescription }}</template>
- </FormSwitch>
-
- <FormInput v-model="localDriveCapacityMb" type="number">
- <span>{{ $ts.driveCapacityPerLocalAccount }}</span>
- <template #suffix>MB</template>
- <template #desc>{{ $ts.inMb }}</template>
- </FormInput>
-
- <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles">
- <span>{{ $ts.driveCapacityPerRemoteAccount }}</span>
- <template #suffix>MB</template>
- <template #desc>{{ $ts.inMb }}</template>
- </FormInput>
-
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
-import * as os from '@/os';
-import * as symbols from '@/symbols';
-import { fetchInstance } from '@/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.files,
- icon: 'fas fa-cloud',
- bg: 'var(--bg)',
- },
- cacheRemoteFiles: false,
- proxyRemoteFiles: false,
- localDriveCapacityMb: 0,
- remoteDriveCapacityMb: 0,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.cacheRemoteFiles = meta.cacheRemoteFiles;
- this.proxyRemoteFiles = meta.proxyRemoteFiles;
- this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
- this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- cacheRemoteFiles: this.cacheRemoteFiles,
- proxyRemoteFiles: this.proxyRemoteFiles,
- localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
- remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue
index 032e394a66..87dd12f489 100644
--- a/packages/client/src/pages/admin/files.vue
+++ b/packages/client/src/pages/admin/files.vue
@@ -19,7 +19,7 @@
<option value="local">{{ $ts.local }}</option>
<option value="remote">{{ $ts.remote }}</option>
</MkSelect>
- <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params().origin === 'local'">
+ <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'">
<template #label>{{ $ts.host }}</template>
</MkInput>
</div>
@@ -55,7 +55,7 @@
</template>
<script lang="ts">
-import { defineComponent } from 'vue';
+import { computed, defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
@@ -95,33 +95,17 @@ export default defineComponent({
type: null,
searchHost: '',
pagination: {
- endpoint: 'admin/drive/files',
+ endpoint: 'admin/drive/files' as const,
limit: 10,
- params: () => ({
+ params: computed(() => ({
type: (this.type && this.type !== '') ? this.type : null,
origin: this.origin,
- hostname: (this.hostname && this.hostname !== '') ? this.hostname : null,
- }),
+ hostname: (this.searchHost && this.searchHost !== '') ? this.searchHost : null,
+ })),
},
}
},
- watch: {
- type() {
- this.$refs.files.reload();
- },
- origin() {
- this.$refs.files.reload();
- },
- searchHost() {
- this.$refs.files.reload();
- },
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
clear() {
os.confirm({
diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue
index e363d1bd03..350e7defc6 100644
--- a/packages/client/src/pages/admin/index.vue
+++ b/packages/client/src/pages/admin/index.vue
@@ -3,14 +3,14 @@
<div v-if="!narrow || page == null" class="nav">
<MkHeader :info="header"></MkHeader>
- <MkSpacer :content-max="700">
+ <MkSpacer :content-max="700" :margin-min="16">
<div class="lxpfedzu">
<div class="banner">
<img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/>
</div>
<MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo>
- <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/bot-protection" class="_link">{{ $ts.configure }}</MkA></MkInfo>
+ <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo>
<MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu>
</div>
@@ -19,7 +19,7 @@
<div class="main">
<MkStickyContainer>
<template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template>
- <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/>
+ <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/>
</MkStickyContainer>
</div>
</div>
@@ -29,9 +29,6 @@
import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { i18n } from '@/i18n';
import MkSuperMenu from '@/components/ui/super-menu.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormButton from '@/components/debobigego/button.vue';
import MkInfo from '@/components/ui/info.vue';
import { scroll } from '@/scripts/scroll';
import { instance } from '@/instance';
@@ -41,10 +38,7 @@ import { lookupUser } from '@/scripts/lookup-user';
export default defineComponent({
components: {
- FormBase,
MkSuperMenu,
- FormGroup,
- FormButton,
MkInfo,
},
@@ -72,7 +66,9 @@ export default defineComponent({
const narrow = ref(false);
const view = ref(null);
const el = ref(null);
- const onInfo = (viewInfo) => {
+ const pageChanged = (page) => {
+ if (page == null) return;
+ const viewInfo = page[symbols.PAGE_INFO];
if (isRef(viewInfo)) {
watch(viewInfo, () => {
childInfo.value = viewInfo.value;
@@ -163,11 +159,6 @@ export default defineComponent({
to: '/admin/settings',
active: page.value === 'settings',
}, {
- icon: 'fas fa-cloud',
- text: i18n.locale.files,
- to: '/admin/files-settings',
- active: page.value === 'files-settings',
- }, {
icon: 'fas fa-envelope',
text: i18n.locale.emailServer,
to: '/admin/email-settings',
@@ -183,11 +174,6 @@ export default defineComponent({
to: '/admin/security',
active: page.value === 'security',
}, {
- icon: 'fas fa-bolt',
- text: 'ServiceWorker',
- to: '/admin/service-worker',
- active: page.value === 'service-worker',
- }, {
icon: 'fas fa-globe',
text: i18n.locale.relays,
to: '/admin/relays',
@@ -236,17 +222,11 @@ export default defineComponent({
case 'database': return defineAsyncComponent(() => import('./database.vue'));
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
- case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue'));
case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue'));
case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue'));
case 'security': return defineAsyncComponent(() => import('./security.vue'));
- case 'bot-protection': return defineAsyncComponent(() => import('./bot-protection.vue'));
- case 'service-worker': return defineAsyncComponent(() => import('./service-worker.vue'));
case 'relays': return defineAsyncComponent(() => import('./relays.vue'));
case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
- case 'integrations/twitter': return defineAsyncComponent(() => import('./integrations-twitter.vue'));
- case 'integrations/github': return defineAsyncComponent(() => import('./integrations-github.vue'));
- case 'integrations/discord': return defineAsyncComponent(() => import('./integrations-discord.vue'));
case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue'));
@@ -333,7 +313,7 @@ export default defineComponent({
narrow,
view,
el,
- onInfo,
+ pageChanged,
childInfo,
pageProps,
component,
diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue
index 2e899de687..6cadc7df39 100644
--- a/packages/client/src/pages/admin/instance-block.vue
+++ b/packages/client/src/pages/admin/instance-block.vue
@@ -1,39 +1,29 @@
<template>
-<FormBase>
+<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <FormTextarea v-model="blockedHosts">
+ <FormTextarea v-model="blockedHosts" class="_formBlock">
<span>{{ $ts.blockedInstances }}</span>
- <template #desc>{{ $ts.blockedInstancesDescription }}</template>
+ <template #caption>{{ $ts.blockedInstancesDescription }}</template>
</FormTextarea>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormTextarea from '@/components/form/textarea.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
FormButton,
FormTextarea,
- FormInfo,
FormSuspense,
},
@@ -50,10 +40,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/instance.vue b/packages/client/src/pages/admin/instance.vue
deleted file mode 100644
index 51fcb8675a..0000000000
--- a/packages/client/src/pages/admin/instance.vue
+++ /dev/null
@@ -1,291 +0,0 @@
-<template>
-<XModalWindow ref="dialog"
- :width="520"
- :height="500"
- @close="$refs.dialog.close()"
- @closed="$emit('closed')"
->
- <template #header>{{ instance.host }}</template>
- <div class="mk-instance-info">
- <div class="_table section">
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.software }}</div>
- <div class="_data">{{ instance.softwareName || '?' }}</div>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.version }}</div>
- <div class="_data">{{ instance.softwareVersion || '?' }}</div>
- </div>
- </div>
- </div>
- <div class="_table data section">
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.registeredAt }}</div>
- <div class="_data">{{ new Date(instance.caughtAt).toLocaleString() }} (<MkTime :time="instance.caughtAt"/>)</div>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.following }}</div>
- <button class="_data _textButton" @click="showFollowing()">{{ number(instance.followingCount) }}</button>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.followers }}</div>
- <button class="_data _textButton" @click="showFollowers()">{{ number(instance.followersCount) }}</button>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.users }}</div>
- <button class="_data _textButton" @click="showUsers()">{{ number(instance.usersCount) }}</button>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.notes }}</div>
- <div class="_data">{{ number(instance.notesCount) }}</div>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.files }}</div>
- <div class="_data">{{ number(instance.driveFiles) }}</div>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.storageUsage }}</div>
- <div class="_data">{{ bytes(instance.driveUsage) }}</div>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.latestRequestSentAt }}</div>
- <div class="_data"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.latestStatus }}</div>
- <div class="_data">{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</div>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.latestRequestReceivedAt }}</div>
- <div class="_data"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div>
- </div>
- </div>
- </div>
- <div class="chart">
- <div class="header">
- <span class="label">{{ $ts.charts }}</span>
- <div class="selects">
- <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
- <option value="instance-requests">{{ $ts._instanceCharts.requests }}</option>
- <option value="instance-users">{{ $ts._instanceCharts.users }}</option>
- <option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option>
- <option value="instance-notes">{{ $ts._instanceCharts.notes }}</option>
- <option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option>
- <option value="instance-ff">{{ $ts._instanceCharts.ff }}</option>
- <option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option>
- <option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option>
- <option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option>
- <option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option>
- <option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option>
- </MkSelect>
- <MkSelect v-model="chartSpan" style="margin: 0;">
- <option value="hour">{{ $ts.perHour }}</option>
- <option value="day">{{ $ts.perDay }}</option>
- </MkSelect>
- </div>
- </div>
- <div class="chart">
- <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart>
- </div>
- </div>
- <div class="operations section">
- <span class="label">{{ $ts.operations }}</span>
- <MkSwitch v-model="isSuspended" class="switch">{{ $ts.stopActivityDelivery }}</MkSwitch>
- <MkSwitch :model-value="isBlocked" class="switch" @update:modelValue="changeBlock">{{ $ts.blockThisInstance }}</MkSwitch>
- <details>
- <summary>{{ $ts.deleteAllFiles }}</summary>
- <MkButton style="margin: 0.5em 0 0.5em 0;" @click="deleteAllFiles()"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton>
- </details>
- <details>
- <summary>{{ $ts.removeAllFollowing }}</summary>
- <MkButton style="margin: 0.5em 0 0.5em 0;" @click="removeAllFollowing()"><i class="fas fa-minus-circle"></i> {{ $ts.removeAllFollowing }}</MkButton>
- <MkInfo warn>{{ $t('removeAllFollowingDescription', { host: instance.host }) }}</MkInfo>
- </details>
- </div>
- <details class="metadata section">
- <summary class="label">{{ $ts.metadata }}</summary>
- <pre><code>{{ JSON.stringify(instance, null, 2) }}</code></pre>
- </details>
- </div>
-</XModalWindow>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
-import MkSelect from '@/components/form/select.vue';
-import MkButton from '@/components/ui/button.vue';
-import MkSwitch from '@/components/form/switch.vue';
-import MkInfo from '@/components/ui/info.vue';
-import MkChart from '@/components/chart.vue';
-import bytes from '@/filters/bytes';
-import number from '@/filters/number';
-import * as os from '@/os';
-
-export default defineComponent({
- components: {
- XModalWindow,
- MkSelect,
- MkButton,
- MkSwitch,
- MkInfo,
- MkChart,
- },
-
- props: {
- instance: {
- type: Object,
- required: true
- }
- },
-
- emits: ['closed'],
-
- data() {
- return {
- isSuspended: this.instance.isSuspended,
- chartSrc: 'requests',
- chartSpan: 'hour',
- };
- },
-
- computed: {
- meta() {
- return this.$instance;
- },
-
- isBlocked() {
- return this.meta && this.meta.blockedHosts && this.meta.blockedHosts.includes(this.instance.host);
- }
- },
-
- watch: {
- isSuspended() {
- os.api('admin/federation/update-instance', {
- host: this.instance.host,
- isSuspended: this.isSuspended
- });
- },
- },
-
- methods: {
- changeBlock(e) {
- os.api('admin/update-meta', {
- blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host)
- });
- },
-
- removeAllFollowing() {
- os.apiWithDialog('admin/federation/remove-all-following', {
- host: this.instance.host
- });
- },
-
- deleteAllFiles() {
- os.apiWithDialog('admin/federation/delete-all-files', {
- host: this.instance.host
- });
- },
-
- showFollowing() {
- // TODO: ページ遷移
- },
-
- showFollowers() {
- // TODO: ページ遷移
- },
-
- showUsers() {
- // TODO: ページ遷移
- },
-
- bytes,
-
- number
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-instance-info {
- overflow: auto;
-
- > .section {
- padding: 16px 32px;
-
- @media (max-width: 500px) {
- padding: 8px 16px;
- }
-
- &:not(:first-child) {
- border-top: solid 0.5px var(--divider);
- }
- }
-
- > .chart {
- border-top: solid 0.5px var(--divider);
- padding: 16px 0 12px 0;
-
- > .header {
- padding: 0 32px;
-
- @media (max-width: 500px) {
- padding: 0 16px;
- }
-
- > .label {
- font-size: 80%;
- opacity: 0.7;
- }
-
- > .selects {
- display: flex;
- }
- }
-
- > .chart {
- padding: 0 16px;
-
- @media (max-width: 500px) {
- padding: 0;
- }
- }
- }
-
- > .operations {
- > .label {
- font-size: 80%;
- opacity: 0.7;
- }
-
- > .switch {
- margin: 16px 0;
- }
- }
-
- > .metadata {
- > .label {
- font-size: 80%;
- opacity: 0.7;
- }
-
- > pre > code {
- display: block;
- max-height: 200px;
- overflow: auto;
- }
- }
-}
-</style>
diff --git a/packages/client/src/pages/admin/integrations-discord.vue b/packages/client/src/pages/admin/integrations.discord.vue
index 383031f3d1..8fc340150a 100644
--- a/packages/client/src/pages/admin/integrations-discord.vue
+++ b/packages/client/src/pages/admin/integrations.discord.vue
@@ -1,37 +1,36 @@
<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableDiscordIntegration">
- {{ $ts.enable }}
+<FormSuspense :p="init">
+ <div class="_formRoot">
+ <FormSwitch v-model="enableDiscordIntegration" class="_formBlock">
+ <template #label>{{ $ts.enable }}</template>
</FormSwitch>
<template v-if="enableDiscordIntegration">
- <FormInfo>Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo>
+ <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo>
- <FormInput v-model="discordClientId">
+ <FormInput v-model="discordClientId" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
- Client ID
+ <template #label>Client ID</template>
</FormInput>
- <FormInput v-model="discordClientSecret">
+ <FormInput v-model="discordClientSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
- Client Secret
+ <template #label>Client Secret</template>
</FormInput>
</template>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
+ <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ </div>
+</FormSuspense>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormInput from '@/components/form/input.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormInfo from '@/components/ui/info.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@@ -40,7 +39,6 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
- FormBase,
FormInfo,
FormButton,
FormSuspense,
@@ -60,10 +58,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/integrations-github.vue b/packages/client/src/pages/admin/integrations.github.vue
index ecb2fd67fa..d9db9c00f1 100644
--- a/packages/client/src/pages/admin/integrations-github.vue
+++ b/packages/client/src/pages/admin/integrations.github.vue
@@ -1,37 +1,36 @@
<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableGithubIntegration">
- {{ $ts.enable }}
+<FormSuspense :p="init">
+ <div class="_formRoot">
+ <FormSwitch v-model="enableGithubIntegration" class="_formBlock">
+ <template #label>{{ $ts.enable }}</template>
</FormSwitch>
<template v-if="enableGithubIntegration">
- <FormInfo>Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo>
+ <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo>
- <FormInput v-model="githubClientId">
+ <FormInput v-model="githubClientId" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
- Client ID
+ <template #label>Client ID</template>
</FormInput>
- <FormInput v-model="githubClientSecret">
+ <FormInput v-model="githubClientSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
- Client Secret
+ <template #label>Client Secret</template>
</FormInput>
</template>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
+ <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ </div>
+</FormSuspense>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormInput from '@/components/form/input.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormInfo from '@/components/ui/info.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@@ -40,7 +39,6 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
- FormBase,
FormInfo,
FormButton,
FormSuspense,
@@ -60,10 +58,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/integrations-twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue
index 1404102c57..1f8074535a 100644
--- a/packages/client/src/pages/admin/integrations-twitter.vue
+++ b/packages/client/src/pages/admin/integrations.twitter.vue
@@ -1,37 +1,36 @@
<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableTwitterIntegration">
- {{ $ts.enable }}
+<FormSuspense :p="init">
+ <div class="_formRoot">
+ <FormSwitch v-model="enableTwitterIntegration" class="_formBlock">
+ <template #label>{{ $ts.enable }}</template>
</FormSwitch>
<template v-if="enableTwitterIntegration">
- <FormInfo>Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo>
+ <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo>
- <FormInput v-model="twitterConsumerKey">
+ <FormInput v-model="twitterConsumerKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
- Consumer Key
+ <template #label>Consumer Key</template>
</FormInput>
- <FormInput v-model="twitterConsumerSecret">
+ <FormInput v-model="twitterConsumerSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
- Consumer Secret
+ <template #label>Consumer Secret</template>
</FormInput>
</template>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
+ <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ </div>
+</FormSuspense>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormInput from '@/components/form/input.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormInfo from '@/components/ui/info.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@@ -40,7 +39,6 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
- FormBase,
FormInfo,
FormButton,
FormSuspense,
@@ -60,10 +58,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue
index c21eebc1c6..91d03fef31 100644
--- a/packages/client/src/pages/admin/integrations.vue
+++ b/packages/client/src/pages/admin/integrations.vue
@@ -1,46 +1,48 @@
<template>
-<FormBase>
+<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <FormLink to="/admin/integrations/twitter">
- <i class="fab fa-twitter"></i> Twitter
+ <FormFolder class="_formBlock">
+ <template #icon><i class="fab fa-twitter"></i></template>
+ <template #label>Twitter</template>
<template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
- <FormLink to="/admin/integrations/github">
- <i class="fab fa-github"></i> GitHub
+ <XTwitter/>
+ </FormFolder>
+ <FormFolder to="/admin/integrations/github" class="_formBlock">
+ <template #icon><i class="fab fa-github"></i></template>
+ <template #label>GitHub</template>
<template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
- <FormLink to="/admin/integrations/discord">
- <i class="fab fa-discord"></i> Discord
+ <XGithub/>
+ </FormFolder>
+ <FormFolder to="/admin/integrations/discord" class="_formBlock">
+ <template #icon><i class="fab fa-discord"></i></template>
+ <template #label>Discord</template>
<template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
+ <XDiscord/>
+ </FormFolder>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormFolder from '@/components/form/folder.vue';
+import FormSecion from '@/components/form/section.vue';
+import FormSuspense from '@/components/form/suspense.vue';
+import XTwitter from './integrations.twitter.vue';
+import XGithub from './integrations.github.vue';
+import XDiscord from './integrations.discord.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
- FormLink,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
+ FormFolder,
+ FormSecion,
FormSuspense,
+ XTwitter,
+ XGithub,
+ XDiscord,
},
emits: ['info'],
@@ -58,10 +60,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue
index 05b64b235c..1de297fd93 100644
--- a/packages/client/src/pages/admin/metrics.vue
+++ b/packages/client/src/pages/admin/metrics.vue
@@ -76,7 +76,6 @@ import MkwFederation from '../../widgets/federation.vue';
import { version, url } from '@/config';
import bytes from '@/filters/bytes';
import number from '@/filters/number';
-import MkInstanceInfo from './instance.vue';
Chart.register(
ArcElement,
@@ -101,6 +100,7 @@ const alpha = (hex, a) => {
return `rgba(${r}, ${g}, ${b}, ${a})`;
};
import * as os from '@/os';
+import { stream } from '@/stream';
export default defineComponent({
components: {
@@ -119,7 +119,7 @@ export default defineComponent({
stats: null,
serverInfo: null,
connection: null,
- queueConnection: markRaw(os.stream.useChannel('queueStats')),
+ queueConnection: markRaw(stream.useChannel('queueStats')),
memUsage: 0,
chartCpuMem: null,
chartNet: null,
@@ -150,7 +150,7 @@ export default defineComponent({
os.api('admin/server-info', {}).then(res => {
this.serverInfo = res;
- this.connection = markRaw(os.stream.useChannel('serverStats'));
+ this.connection = markRaw(stream.useChannel('serverStats'));
this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send('requestLog', {
diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue
index 8984686b5e..6c5be220f8 100644
--- a/packages/client/src/pages/admin/object-storage.vue
+++ b/packages/client/src/pages/admin/object-storage.vue
@@ -1,76 +1,78 @@
<template>
-<FormBase>
+<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <FormSwitch v-model="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch>
+ <div class="_formRoot">
+ <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch>
- <template v-if="useObjectStorage">
- <FormInput v-model="objectStorageBaseUrl">
- <span>{{ $ts.objectStorageBaseUrl }}</span>
- <template #desc>{{ $ts.objectStorageBaseUrlDesc }}</template>
- </FormInput>
+ <template v-if="useObjectStorage">
+ <FormInput v-model="objectStorageBaseUrl" class="_formBlock">
+ <template #label>{{ $ts.objectStorageBaseUrl }}</template>
+ <template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template>
+ </FormInput>
- <FormInput v-model="objectStorageBucket">
- <span>{{ $ts.objectStorageBucket }}</span>
- <template #desc>{{ $ts.objectStorageBucketDesc }}</template>
- </FormInput>
+ <FormInput v-model="objectStorageBucket" class="_formBlock">
+ <template #label>{{ $ts.objectStorageBucket }}</template>
+ <template #caption>{{ $ts.objectStorageBucketDesc }}</template>
+ </FormInput>
- <FormInput v-model="objectStoragePrefix">
- <span>{{ $ts.objectStoragePrefix }}</span>
- <template #desc>{{ $ts.objectStoragePrefixDesc }}</template>
- </FormInput>
+ <FormInput v-model="objectStoragePrefix" class="_formBlock">
+ <template #label>{{ $ts.objectStoragePrefix }}</template>
+ <template #caption>{{ $ts.objectStoragePrefixDesc }}</template>
+ </FormInput>
- <FormInput v-model="objectStorageEndpoint">
- <span>{{ $ts.objectStorageEndpoint }}</span>
- <template #desc>{{ $ts.objectStorageEndpointDesc }}</template>
- </FormInput>
+ <FormInput v-model="objectStorageEndpoint" class="_formBlock">
+ <template #label>{{ $ts.objectStorageEndpoint }}</template>
+ <template #caption>{{ $ts.objectStorageEndpointDesc }}</template>
+ </FormInput>
- <FormInput v-model="objectStorageRegion">
- <span>{{ $ts.objectStorageRegion }}</span>
- <template #desc>{{ $ts.objectStorageRegionDesc }}</template>
- </FormInput>
+ <FormInput v-model="objectStorageRegion" class="_formBlock">
+ <template #label>{{ $ts.objectStorageRegion }}</template>
+ <template #caption>{{ $ts.objectStorageRegionDesc }}</template>
+ </FormInput>
- <FormInput v-model="objectStorageAccessKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>Access key</span>
- </FormInput>
+ <FormSplit :min-width="280">
+ <FormInput v-model="objectStorageAccessKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>Access key</template>
+ </FormInput>
- <FormInput v-model="objectStorageSecretKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>Secret key</span>
- </FormInput>
+ <FormInput v-model="objectStorageSecretKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>Secret key</template>
+ </FormInput>
+ </FormSplit>
- <FormSwitch v-model="objectStorageUseSSL">
- {{ $ts.objectStorageUseSSL }}
- <template #desc>{{ $ts.objectStorageUseSSLDesc }}</template>
- </FormSwitch>
+ <FormSwitch v-model="objectStorageUseSSL" class="_formBlock">
+ <template #label>{{ $ts.objectStorageUseSSL }}</template>
+ <template #caption>{{ $ts.objectStorageUseSSLDesc }}</template>
+ </FormSwitch>
- <FormSwitch v-model="objectStorageUseProxy">
- {{ $ts.objectStorageUseProxy }}
- <template #desc>{{ $ts.objectStorageUseProxyDesc }}</template>
- </FormSwitch>
+ <FormSwitch v-model="objectStorageUseProxy" class="_formBlock">
+ <template #label>{{ $ts.objectStorageUseProxy }}</template>
+ <template #caption>{{ $ts.objectStorageUseProxyDesc }}</template>
+ </FormSwitch>
- <FormSwitch v-model="objectStorageSetPublicRead">
- {{ $ts.objectStorageSetPublicRead }}
- </FormSwitch>
+ <FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock">
+ <template #label>{{ $ts.objectStorageSetPublicRead }}</template>
+ </FormSwitch>
- <FormSwitch v-model="objectStorageS3ForcePathStyle">
- s3ForcePathStyle
- </FormSwitch>
- </template>
-
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ <FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock">
+ <template #label>s3ForcePathStyle</template>
+ </FormSwitch>
+ </template>
+ </div>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormInput from '@/components/form/input.vue';
+import FormGroup from '@/components/form/group.vue';
+import FormSuspense from '@/components/form/suspense.vue';
+import FormSplit from '@/components/form/split.vue';
+import FormSection from '@/components/form/section.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@@ -79,10 +81,10 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
- FormBase,
FormGroup,
- FormButton,
FormSuspense,
+ FormSplit,
+ FormSection,
},
emits: ['info'],
@@ -93,6 +95,12 @@ export default defineComponent({
title: this.$ts.objectStorage,
icon: 'fas fa-cloud',
bg: 'var(--bg)',
+ actions: [{
+ asFullButton: true,
+ icon: 'fas fa-check',
+ text: this.$ts.save,
+ handler: this.save,
+ }],
},
useObjectStorage: false,
objectStorageBaseUrl: null,
@@ -110,10 +118,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue
index eb214a21c8..6b588e88aa 100644
--- a/packages/client/src/pages/admin/other-settings.vue
+++ b/packages/client/src/pages/admin/other-settings.vue
@@ -1,34 +1,17 @@
<template>
-<FormBase>
+<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <FormGroup>
- <FormInput v-model="summalyProxy">
- <template #prefix><i class="fas fa-link"></i></template>
- Summaly Proxy URL
- </FormInput>
- </FormGroup>
- <FormGroup>
- <FormInput v-model="deeplAuthKey">
- <template #prefix><i class="fas fa-key"></i></template>
- DeepL Auth Key
- </FormInput>
- <FormSwitch v-model="deeplIsPro">
- Pro account
- </FormSwitch>
- </FormGroup>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ none
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormInput from '@/components/form/input.vue';
+import FormSection from '@/components/form/section.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@@ -37,9 +20,7 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
- FormBase,
- FormGroup,
- FormButton,
+ FormSection,
FormSuspense,
},
@@ -51,29 +32,22 @@ export default defineComponent({
title: this.$ts.other,
icon: 'fas fa-cogs',
bg: 'var(--bg)',
+ actions: [{
+ asFullButton: true,
+ icon: 'fas fa-check',
+ text: this.$ts.save,
+ handler: this.save,
+ }],
},
- summalyProxy: '',
- deeplAuthKey: '',
- deeplIsPro: false,
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
- this.summalyProxy = meta.summalyProxy;
- this.deeplAuthKey = meta.deeplAuthKey;
- this.deeplIsPro = meta.deeplIsPro;
},
save() {
os.apiWithDialog('admin/update-meta', {
- summalyProxy: this.summalyProxy,
- deeplAuthKey: this.deeplAuthKey,
- deeplIsPro: this.deeplIsPro,
}).then(() => {
fetchInstance();
});
diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue
index da5fc0ba6d..b8ae8ad9e1 100644
--- a/packages/client/src/pages/admin/overview.vue
+++ b/packages/client/src/pages/admin/overview.vue
@@ -19,7 +19,7 @@
<MkContainer :foldable="true" class="charts">
<template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template>
- <div style="padding-top: 12px;">
+ <div style="padding: 12px;">
<MkInstanceStats :chart-limit="500" :detailed="true"/>
</div>
</MkContainer>
@@ -67,7 +67,6 @@
<script lang="ts">
import { computed, defineComponent, markRaw, version as vueVersion } from 'vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
import MkInstanceStats from '@/components/instance-stats.vue';
import MkButton from '@/components/ui/button.vue';
import MkSelect from '@/components/form/select.vue';
@@ -78,15 +77,14 @@ import MkQueueChart from '@/components/queue-chart.vue';
import { version, url } from '@/config';
import bytes from '@/filters/bytes';
import number from '@/filters/number';
-import MkInstanceInfo from './instance.vue';
import XMetrics from './metrics.vue';
import * as os from '@/os';
+import { stream } from '@/stream';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
MkNumberDiff,
- FormKeyValueView,
MkInstanceStats,
MkContainer,
MkFolder,
@@ -113,13 +111,11 @@ export default defineComponent({
notesComparedToThePrevDay: null,
fetchJobs: () => os.api('admin/queue/deliver-delayed', {}),
fetchModLogs: () => os.api('admin/show-moderation-logs', {}),
- queueStatsConnection: markRaw(os.stream.useChannel('queueStats')),
+ queueStatsConnection: markRaw(stream.useChannel('queueStats')),
}
},
async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
os.api('meta', { detail: true }).then(meta => {
this.meta = meta;
});
@@ -160,9 +156,7 @@ export default defineComponent({
host: q
});
}
- os.popup(MkInstanceInfo, {
- instance: instance
- }, {}, 'closed');
+ // TODO
},
bytes,
diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue
index 14ef92a747..5c4fbffa0c 100644
--- a/packages/client/src/pages/admin/proxy-account.vue
+++ b/packages/client/src/pages/admin/proxy-account.vue
@@ -1,42 +1,32 @@
<template>
-<FormBase>
+<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts.proxyAccount }}</template>
- <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template>
- </FormKeyValueView>
- <template #caption>{{ $ts.proxyAccountDescription }}</template>
- </FormGroup>
+ <MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo>
+ <MkKeyValue class="_formBlock">
+ <template #key>{{ $ts.proxyAccount }}</template>
+ <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template>
+ </MkKeyValue>
- <FormButton primary @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton>
+ <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import MkKeyValue from '@/components/key-value.vue';
+import FormButton from '@/components/ui/button.vue';
+import MkInfo from '@/components/ui/info.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
- FormKeyValueView,
- FormInput,
- FormBase,
- FormGroup,
+ MkKeyValue,
FormButton,
- FormTextarea,
- FormInfo,
+ MkInfo,
FormSuspense,
},
@@ -54,10 +44,6 @@ export default defineComponent({
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue
index 37a87089cb..522210d933 100644
--- a/packages/client/src/pages/admin/queue.vue
+++ b/packages/client/src/pages/admin/queue.vue
@@ -1,28 +1,25 @@
<template>
-<FormBase>
+<MkSpacer :content-max="800">
<XQueue :connection="connection" domain="inbox">
<template #title>In</template>
</XQueue>
<XQueue :connection="connection" domain="deliver">
<template #title>Out</template>
</XQueue>
- <FormButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</FormButton>
-</FormBase>
+ <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import MkButton from '@/components/ui/button.vue';
import XQueue from './queue.chart.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormButton from '@/components/debobigego/button.vue';
import * as os from '@/os';
+import { stream } from '@/stream';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
- FormButton,
MkButton,
XQueue,
},
@@ -36,13 +33,11 @@ export default defineComponent({
icon: 'fas fa-clipboard-list',
bg: 'var(--bg)',
},
- connection: markRaw(os.stream.useChannel('queueStats')),
+ connection: markRaw(stream.useChannel('queueStats')),
}
},
mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
this.$nextTick(() => {
this.connection.send('requestLog', {
id: Math.random().toString().substr(2, 8),
diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue
index 3e2f1c6f26..bb840db0a2 100644
--- a/packages/client/src/pages/admin/relays.vue
+++ b/packages/client/src/pages/admin/relays.vue
@@ -1,32 +1,27 @@
<template>
-<FormBase class="relaycxt">
- <FormButton primary @click="addRelay"><i class="fas fa-plus"></i> {{ $ts.addRelay }}</FormButton>
-
- <div v-for="relay in relays" :key="relay.inbox" class="_debobigegoItem">
- <div class="_debobigegoPanel" style="padding: 16px;">
- <div>{{ relay.inbox }}</div>
- <div>{{ $t(`_relayStatus.${relay.status}`) }}</div>
- <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
+<MkSpacer :content-max="800">
+ <div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel _block" style="padding: 16px;">
+ <div>{{ relay.inbox }}</div>
+ <div class="status">
+ <i v-if="relay.status === 'accepted'" class="fas fa-check icon accepted"></i>
+ <i v-else-if="relay.status === 'rejected'" class="fas fa-ban icon rejected"></i>
+ <i v-else class="fas fa-clock icon requesting"></i>
+ <span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
</div>
+ <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
</div>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
-import MkInput from '@/components/form/input.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormButton from '@/components/debobigego/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
- FormButton,
MkButton,
- MkInput,
},
emits: ['info'],
@@ -37,6 +32,12 @@ export default defineComponent({
title: this.$ts.relays,
icon: 'fas fa-globe',
bg: 'var(--bg)',
+ actions: [{
+ asFullButton: true,
+ icon: 'fas fa-plus',
+ text: this.$ts.addRelay,
+ handler: this.addRelay,
+ }],
},
relays: [],
inbox: '',
@@ -47,10 +48,6 @@ export default defineComponent({
this.refresh();
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async addRelay() {
const { canceled, result: inbox } = await os.inputText({
@@ -94,5 +91,22 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
+.relaycxt {
+ > .status {
+ margin: 8px 0;
+
+ > .icon {
+ width: 1em;
+ margin-right: 0.75em;
+ &.accepted {
+ color: var(--success);
+ }
+
+ &.rejected {
+ color: var(--error);
+ }
+ }
+ }
+}
</style>
diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue
index adfb2e786c..d069891647 100644
--- a/packages/client/src/pages/admin/security.vue
+++ b/packages/client/src/pages/admin/security.vue
@@ -1,44 +1,58 @@
<template>
-<FormBase>
+<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <FormLink to="/admin/bot-protection">
- <i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}
- <template v-if="enableHcaptcha" #suffix>hCaptcha</template>
- <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
- <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template>
- </FormLink>
+ <div class="_formRoot">
+ <FormFolder class="_formBlock">
+ <template #icon><i class="fas fa-shield-alt"></i></template>
+ <template #label>{{ $ts.botProtection }}</template>
+ <template v-if="enableHcaptcha" #suffix>hCaptcha</template>
+ <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
+ <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template>
- <FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch>
+ <XBotProtection/>
+ </FormFolder>
- <FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch>
+ <FormFolder class="_formBlock">
+ <template #label>Summaly Proxy</template>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ <div class="_formRoot">
+ <FormInput v-model="summalyProxy" class="_formBlock">
+ <template #prefix><i class="fas fa-link"></i></template>
+ <template #label>Summaly Proxy URL</template>
+ </FormInput>
+
+ <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ </div>
+ </FormFolder>
+ </div>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormFolder from '@/components/form/folder.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormInfo from '@/components/ui/info.vue';
+import FormSuspense from '@/components/form/suspense.vue';
+import FormSection from '@/components/form/section.vue';
+import FormInput from '@/components/form/input.vue';
+import FormButton from '@/components/ui/button.vue';
+import XBotProtection from './bot-protection.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
- FormLink,
+ FormFolder,
FormSwitch,
- FormBase,
- FormGroup,
- FormButton,
FormInfo,
+ FormSection,
FormSuspense,
+ FormButton,
+ FormInput,
+ XBotProtection,
},
emits: ['info'],
@@ -50,30 +64,23 @@ export default defineComponent({
icon: 'fas fa-lock',
bg: 'var(--bg)',
},
+ summalyProxy: '',
enableHcaptcha: false,
enableRecaptcha: false,
- enableRegistration: false,
- emailRequiredForSignup: false,
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
+ this.summalyProxy = meta.summalyProxy;
this.enableHcaptcha = meta.enableHcaptcha;
this.enableRecaptcha = meta.enableRecaptcha;
- this.enableRegistration = !meta.disableRegistration;
- this.emailRequiredForSignup = meta.emailRequiredForSignup;
},
-
+
save() {
os.apiWithDialog('admin/update-meta', {
- disableRegistration: !this.enableRegistration,
- emailRequiredForSignup: this.emailRequiredForSignup,
+ summalyProxy: this.summalyProxy,
}).then(() => {
fetchInstance();
});
diff --git a/packages/client/src/pages/admin/service-worker.vue b/packages/client/src/pages/admin/service-worker.vue
deleted file mode 100644
index f34cb03e4e..0000000000
--- a/packages/client/src/pages/admin/service-worker.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableServiceWorker">
- {{ $ts.enableServiceworker }}
- <template #desc>{{ $ts.serviceworkerInfo }}</template>
- </FormSwitch>
-
- <template v-if="enableServiceWorker">
- <FormInput v-model="swPublicKey">
- <template #prefix><i class="fas fa-key"></i></template>
- Public key
- </FormInput>
-
- <FormInput v-model="swPrivateKey">
- <template #prefix><i class="fas fa-key"></i></template>
- Private key
- </FormInput>
- </template>
-
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
-import * as os from '@/os';
-import * as symbols from '@/symbols';
-import { fetchInstance } from '@/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'ServiceWorker',
- icon: 'fas fa-bolt',
- bg: 'var(--bg)',
- },
- enableServiceWorker: false,
- swPublicKey: null,
- swPrivateKey: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableServiceWorker = meta.enableServiceWorker;
- this.swPublicKey = meta.swPublickey;
- this.swPrivateKey = meta.swPrivateKey;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableServiceWorker: this.enableServiceWorker,
- swPublicKey: this.swPublicKey,
- swPrivateKey: this.swPrivateKey,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue
index d88445abdb..a4bac93834 100644
--- a/packages/client/src/pages/admin/settings.vue
+++ b/packages/client/src/pages/admin/settings.vue
@@ -1,72 +1,146 @@
<template>
-<FormBase>
+<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <FormInput v-model="name">
- <span>{{ $ts.instanceName }}</span>
- </FormInput>
+ <div class="_formRoot">
+ <FormInput v-model="name" class="_formBlock">
+ <template #label>{{ $ts.instanceName }}</template>
+ </FormInput>
- <FormTextarea v-model="description">
- <span>{{ $ts.instanceDescription }}</span>
- </FormTextarea>
+ <FormTextarea v-model="description" class="_formBlock">
+ <template #label>{{ $ts.instanceDescription }}</template>
+ </FormTextarea>
- <FormInput v-model="iconUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.iconUrl }}</span>
- </FormInput>
+ <FormInput v-model="iconUrl" class="_formBlock">
+ <template #prefix><i class="fas fa-link"></i></template>
+ <template #label>{{ $ts.iconUrl }}</template>
+ </FormInput>
- <FormInput v-model="bannerUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.bannerUrl }}</span>
- </FormInput>
+ <FormInput v-model="bannerUrl" class="_formBlock">
+ <template #prefix><i class="fas fa-link"></i></template>
+ <template #label>{{ $ts.bannerUrl }}</template>
+ </FormInput>
- <FormInput v-model="backgroundImageUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.backgroundImageUrl }}</span>
- </FormInput>
+ <FormInput v-model="backgroundImageUrl" class="_formBlock">
+ <template #prefix><i class="fas fa-link"></i></template>
+ <template #label>{{ $ts.backgroundImageUrl }}</template>
+ </FormInput>
- <FormInput v-model="tosUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.tosUrl }}</span>
- </FormInput>
+ <FormInput v-model="tosUrl" class="_formBlock">
+ <template #prefix><i class="fas fa-link"></i></template>
+ <template #label>{{ $ts.tosUrl }}</template>
+ </FormInput>
- <FormInput v-model="maintainerName">
- <span>{{ $ts.maintainerName }}</span>
- </FormInput>
+ <FormSplit :min-width="300">
+ <FormInput v-model="maintainerName" class="_formBlock">
+ <template #label>{{ $ts.maintainerName }}</template>
+ </FormInput>
- <FormInput v-model="maintainerEmail" type="email">
- <template #prefix><i class="fas fa-envelope"></i></template>
- <span>{{ $ts.maintainerEmail }}</span>
- </FormInput>
+ <FormInput v-model="maintainerEmail" type="email" class="_formBlock">
+ <template #prefix><i class="fas fa-envelope"></i></template>
+ <template #label>{{ $ts.maintainerEmail }}</template>
+ </FormInput>
+ </FormSplit>
- <FormTextarea v-model="pinnedUsers">
- <span>{{ $ts.pinnedUsers }}</span>
- <template #desc>{{ $ts.pinnedUsersDescription }}</template>
- </FormTextarea>
+ <FormTextarea v-model="pinnedUsers" class="_formBlock">
+ <template #label>{{ $ts.pinnedUsers }}</template>
+ <template #caption>{{ $ts.pinnedUsersDescription }}</template>
+ </FormTextarea>
- <FormInput v-model="maxNoteTextLength" type="number">
- <template #prefix><i class="fas fa-pencil-alt"></i></template>
- <span>{{ $ts.maxNoteTextLength }}</span>
- </FormInput>
+ <FormInput v-model="maxNoteTextLength" type="number" class="_formBlock">
+ <template #prefix><i class="fas fa-pencil-alt"></i></template>
+ <template #label>{{ $ts.maxNoteTextLength }}</template>
+ </FormInput>
- <FormSwitch v-model="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch>
- <FormSwitch v-model="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch>
- <FormInfo>{{ $ts.disablingTimelinesInfo }}</FormInfo>
+ <FormSection>
+ <FormSwitch v-model="enableRegistration" class="_formBlock">
+ <template #label>{{ $ts.enableRegistration }}</template>
+ </FormSwitch>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ <FormSwitch v-model="emailRequiredForSignup" class="_formBlock">
+ <template #label>{{ $ts.emailRequiredForSignup }}</template>
+ </FormSwitch>
+ </FormSection>
+
+ <FormSection>
+ <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch>
+ <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch>
+ <FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo>
+ </FormSection>
+
+ <FormSection>
+ <template #label>{{ $ts.files }}</template>
+
+ <FormSwitch v-model="cacheRemoteFiles" class="_formBlock">
+ <template #label>{{ $ts.cacheRemoteFiles }}</template>
+ <template #caption>{{ $ts.cacheRemoteFilesDescription }}</template>
+ </FormSwitch>
+
+ <FormSwitch v-model="proxyRemoteFiles" class="_formBlock">
+ <template #label>{{ $ts.proxyRemoteFiles }}</template>
+ <template #caption>{{ $ts.proxyRemoteFilesDescription }}</template>
+ </FormSwitch>
+
+ <FormSplit :min-width="280">
+ <FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock">
+ <template #label>{{ $ts.driveCapacityPerLocalAccount }}</template>
+ <template #suffix>MB</template>
+ <template #caption>{{ $ts.inMb }}</template>
+ </FormInput>
+
+ <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock">
+ <template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template>
+ <template #suffix>MB</template>
+ <template #caption>{{ $ts.inMb }}</template>
+ </FormInput>
+ </FormSplit>
+ </FormSection>
+
+ <FormSection>
+ <template #label>ServiceWorker</template>
+
+ <FormSwitch v-model="enableServiceWorker" class="_formBlock">
+ <template #label>{{ $ts.enableServiceworker }}</template>
+ <template #caption>{{ $ts.serviceworkerInfo }}</template>
+ </FormSwitch>
+
+ <template v-if="enableServiceWorker">
+ <FormInput v-model="swPublicKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>Public key</template>
+ </FormInput>
+
+ <FormInput v-model="swPrivateKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>Private key</template>
+ </FormInput>
+ </template>
+ </FormSection>
+
+ <FormSection>
+ <template #label>DeepL Translation</template>
+
+ <FormInput v-model="deeplAuthKey" class="_formBlock">
+ <template #prefix><i class="fas fa-key"></i></template>
+ <template #label>DeepL Auth Key</template>
+ </FormInput>
+ <FormSwitch v-model="deeplIsPro" class="_formBlock">
+ <template #label>Pro account</template>
+ </FormSwitch>
+ </FormSection>
+ </div>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormInput from '@/components/form/input.vue';
+import FormTextarea from '@/components/form/textarea.vue';
+import FormInfo from '@/components/ui/info.vue';
+import FormSection from '@/components/form/section.vue';
+import FormSplit from '@/components/form/split.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@@ -75,12 +149,11 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
- FormBase,
- FormGroup,
- FormButton,
+ FormSuspense,
FormTextarea,
FormInfo,
- FormSuspense,
+ FormSection,
+ FormSplit,
},
emits: ['info'],
@@ -91,6 +164,12 @@ export default defineComponent({
title: this.$ts.general,
icon: 'fas fa-cog',
bg: 'var(--bg)',
+ actions: [{
+ asFullButton: true,
+ icon: 'fas fa-check',
+ text: this.$ts.save,
+ handler: this.save,
+ }],
},
name: null,
description: null,
@@ -104,13 +183,20 @@ export default defineComponent({
enableLocalTimeline: false,
enableGlobalTimeline: false,
pinnedUsers: '',
+ cacheRemoteFiles: false,
+ proxyRemoteFiles: false,
+ localDriveCapacityMb: 0,
+ remoteDriveCapacityMb: 0,
+ enableRegistration: false,
+ emailRequiredForSignup: false,
+ enableServiceWorker: false,
+ swPublicKey: null,
+ swPrivateKey: null,
+ deeplAuthKey: '',
+ deeplIsPro: false,
}
},
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
@@ -126,6 +212,17 @@ export default defineComponent({
this.enableLocalTimeline = !meta.disableLocalTimeline;
this.enableGlobalTimeline = !meta.disableGlobalTimeline;
this.pinnedUsers = meta.pinnedUsers.join('\n');
+ this.cacheRemoteFiles = meta.cacheRemoteFiles;
+ this.proxyRemoteFiles = meta.proxyRemoteFiles;
+ this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
+ this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
+ this.enableRegistration = !meta.disableRegistration;
+ this.emailRequiredForSignup = meta.emailRequiredForSignup;
+ this.enableServiceWorker = meta.enableServiceWorker;
+ this.swPublicKey = meta.swPublickey;
+ this.swPrivateKey = meta.swPrivateKey;
+ this.deeplAuthKey = meta.deeplAuthKey;
+ this.deeplIsPro = meta.deeplIsPro;
},
save() {
@@ -142,6 +239,17 @@ export default defineComponent({
disableLocalTimeline: !this.enableLocalTimeline,
disableGlobalTimeline: !this.enableGlobalTimeline,
pinnedUsers: this.pinnedUsers.split('\n'),
+ cacheRemoteFiles: this.cacheRemoteFiles,
+ proxyRemoteFiles: this.proxyRemoteFiles,
+ localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
+ remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
+ disableRegistration: !this.enableRegistration,
+ emailRequiredForSignup: this.emailRequiredForSignup,
+ enableServiceWorker: this.enableServiceWorker,
+ swPublicKey: this.swPublicKey,
+ swPrivateKey: this.swPrivateKey,
+ deeplAuthKey: this.deeplAuthKey,
+ deeplIsPro: this.deeplIsPro,
}).then(() => {
fetchInstance();
});
diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue
index e7a3437167..03e155ddcf 100644
--- a/packages/client/src/pages/admin/users.vue
+++ b/packages/client/src/pages/admin/users.vue
@@ -30,7 +30,7 @@
<template #prefix>@</template>
<template #label>{{ $ts.username }}</template>
</MkInput>
- <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'" @update:modelValue="$refs.users.reload()">
+ <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()">
<template #prefix>@</template>
<template #label>{{ $ts.host }}</template>
</MkInput>
@@ -62,7 +62,7 @@
</template>
<script lang="ts">
-import { defineComponent } from 'vue';
+import { computed, defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
@@ -110,36 +110,20 @@ export default defineComponent({
searchUsername: '',
searchHost: '',
pagination: {
- endpoint: 'admin/show-users',
+ endpoint: 'admin/show-users' as const,
limit: 10,
- params: () => ({
+ params: computed(() => ({
sort: this.sort,
state: this.state,
origin: this.origin,
username: this.searchUsername,
hostname: this.searchHost,
- }),
+ })),
offsetMode: true
},
}
},
- watch: {
- sort() {
- this.$refs.users.reload();
- },
- state() {
- this.$refs.users.reload();
- },
- origin() {
- this.$refs.users.reload();
- },
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
lookupUser,
diff --git a/packages/client/src/pages/advanced-theme-editor.vue b/packages/client/src/pages/advanced-theme-editor.vue
deleted file mode 100644
index 9c2423131c..0000000000
--- a/packages/client/src/pages/advanced-theme-editor.vue
+++ /dev/null
@@ -1,349 +0,0 @@
-<template>
-<div class="t9makv94">
- <section class="_section">
- <div class="_content">
- <details>
- <summary>{{ $ts.import }}</summary>
- <MkTextarea v-model="themeToImport">
- {{ $ts._theme.importInfo }}
- </MkTextarea>
- <MkButton :disabled="!themeToImport.trim()" @click="importTheme">{{ $ts.import }}</MkButton>
- </details>
- </div>
- </section>
- <section class="_section">
- <div class="_content _card _gap">
- <div class="_content">
- <MkInput v-model="name" required><span>{{ $ts.name }}</span></MkInput>
- <MkInput v-model="author" required><span>{{ $ts.author }}</span></MkInput>
- <MkTextarea v-model="description"><span>{{ $ts.description }}</span></MkTextarea>
- <div class="_inputs">
- <div v-text="$ts._theme.base" />
- <MkRadio v-model="baseTheme" value="light">{{ $ts.light }}</MkRadio>
- <MkRadio v-model="baseTheme" value="dark">{{ $ts.dark }}</MkRadio>
- </div>
- </div>
- </div>
- <div class="_content _card _gap">
- <div class="list-view _content">
- <div v-for="([ k, v ], i) in theme" :key="k" class="item">
- <div class="_inputs">
- <div>
- {{ k.startsWith('$') ? `${k} (${$ts._theme.constant})` : $t('_theme.keys.' + k) }}
- <button v-if="k.startsWith('$')" class="_button _link" @click="del(i)" v-text="$ts.delete" />
- </div>
- <div>
- <div class="type" @click="chooseType($event, i)">
- {{ getTypeOf(v) }} <i class="fas fa-chevron-down"></i>
- </div>
- <!-- default -->
- <div v-if="v === null" class="default-value" v-text="baseProps[k]" />
- <!-- color -->
- <div v-else-if="typeof v === 'string'" class="color">
- <input type="color" :value="v" @input="colorChanged($event.target.value, i)"/>
- <MkInput class="select" :value="v" @update:modelValue="colorChanged($event, i)"/>
- </div>
- <!-- ref const -->
- <MkInput v-else-if="v.type === 'refConst'" v-model="v.key">
- <template #prefix>$</template>
- <span>{{ $ts.name }}</span>
- </MkInput>
- <!-- ref props -->
- <MkSelect v-else-if="v.type === 'refProp'" v-model="v.key" class="select">
- <option v-for="key in themeProps" :key="key" :value="key">{{ $t('_theme.keys.' + key) }}</option>
- </MkSelect>
- <!-- func -->
- <template v-else-if="v.type === 'func'">
- <MkSelect v-model="v.name" class="select">
- <template #label>{{ $ts._theme.funcKind }}</template>
- <option v-for="n in ['alpha', 'darken', 'lighten']" :key="n" :value="n">{{ $t('_theme.' + n) }}</option>
- </MkSelect>
- <MkInput v-model="v.arg" type="number"><span>{{ $ts._theme.argument }}</span></MkInput>
- <MkSelect v-model="v.value" class="select">
- <template #label>{{ $ts._theme.basedProp }}</template>
- <option v-for="key in themeProps" :key="key" :value="key">{{ $t('_theme.keys.' + key) }}</option>
- </MkSelect>
- </template>
- <!-- CSS -->
- <MkInput v-else-if="v.type === 'css'" v-model="v.value">
- <span>CSS</span>
- </MkInput>
- </div>
- </div>
- </div>
- <MkButton primary @click="addConst">{{ $ts._theme.addConstant }}</MkButton>
- </div>
- </div>
- </section>
- <section class="_section">
- <details class="_content">
- <summary>{{ $ts.sample }}</summary>
- <MkSample/>
- </details>
- </section>
- <section class="_section">
- <div class="_content">
- <MkButton inline @click="preview">{{ $ts.preview }}</MkButton>
- <MkButton inline primary :disabled="!name || !author" @click="save">{{ $ts.save }}</MkButton>
- </div>
- </section>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import { toUnicode } from 'punycode/';
-
-import MkRadio from '@/components/form/radio.vue';
-import MkButton from '@/components/ui/button.vue';
-import MkInput from '@/components/form/input.vue';
-import MkTextarea from '@/components/form/textarea.vue';
-import MkSelect from '@/components/form/select.vue';
-import MkSample from '@/components/sample.vue';
-
-import { convertToMisskeyTheme, ThemeValue, convertToViewModel, ThemeViewModel } from '@/scripts/theme-editor';
-import { Theme, applyTheme, lightTheme, darkTheme, themeProps, validateTheme } from '@/scripts/theme';
-import { host } from '@/config';
-import * as os from '@/os';
-import { ColdDeviceStorage } from '@/store';
-import { addTheme } from '@/theme-store';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- MkRadio,
- MkButton,
- MkInput,
- MkTextarea,
- MkSelect,
- MkSample,
- },
-
- async beforeRouteLeave(to, from, next) {
- if (this.changed && !(await this.confirm())) {
- next(false);
- } else {
- next();
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.themeEditor,
- icon: 'fas fa-palette',
- },
- theme: [] as ThemeViewModel,
- name: '',
- description: '',
- baseTheme: 'light' as 'dark' | 'light',
- author: `@${this.$i.username}@${toUnicode(host)}`,
- themeToImport: '',
- changed: false,
- lightTheme, darkTheme, themeProps,
- }
- },
-
- computed: {
- baseProps() {
- return this.baseTheme === 'light' ? this.lightTheme.props : this.darkTheme.props;
- },
- },
-
- beforeUnmount() {
- window.removeEventListener('beforeunload', this.beforeunload);
- },
-
- mounted() {
- this.init();
- window.addEventListener('beforeunload', this.beforeunload);
- const changed = () => this.changed = true;
- this.$watch('name', changed);
- this.$watch('description', changed);
- this.$watch('baseTheme', changed);
- this.$watch('author', changed);
- this.$watch('theme', changed);
- },
-
- methods: {
- beforeunload(e: BeforeUnloadEvent) {
- if (this.changed) {
- e.preventDefault();
- e.returnValue = '';
- }
- },
-
- async confirm(): Promise<boolean> {
- const { canceled } = await os.confirm({
- type: 'warning',
- text: this.$ts.leaveConfirm,
- });
- return !canceled;
- },
-
- init() {
- const t: ThemeViewModel = [];
- for (const key of themeProps) {
- t.push([ key, null ]);
- }
- this.theme = t;
- },
-
- async del(i: number) {
- const { canceled } = await os.confirm({
- type: 'warning',
- text: this.$t('_theme.deleteConstantConfirm', { const: this.theme[i][0] }),
- });
- if (canceled) return;
- Vue.delete(this.theme, i);
- },
-
- async addConst() {
- const { canceled, result } = await os.inputText({
- title: this.$ts._theme.inputConstantName,
- });
- if (canceled) return;
- this.theme.push([ '$' + result, '#000000']);
- },
-
- save() {
- const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme);
- addTheme(theme);
- os.alert({
- type: 'success',
- text: this.$t('_theme.installed', { name: theme.name })
- });
- this.changed = false;
- },
-
- preview() {
- const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme);
- try {
- applyTheme(theme, false);
- } catch (e) {
- os.alert({
- type: 'error',
- text: e.message
- });
- }
- },
-
- async importTheme() {
- if (this.changed && (!await this.confirm())) return;
-
- try {
- const theme = JSON5.parse(this.themeToImport) as Theme;
- if (!validateTheme(theme)) throw new Error(this.$ts._theme.invalid);
-
- this.name = theme.name;
- this.description = theme.desc || '';
- this.author = theme.author;
- this.baseTheme = theme.base || 'light';
- this.theme = convertToViewModel(theme);
- this.themeToImport = '';
- } catch (e) {
- os.alert({
- type: 'error',
- text: e.message
- });
- }
- },
-
- colorChanged(color: string, i: number) {
- this.theme[i] = [this.theme[i][0], color];
- },
-
- getTypeOf(v: ThemeValue) {
- return v === null
- ? this.$ts._theme.defaultValue
- : typeof v === 'string'
- ? this.$ts._theme.color
- : this.$t('_theme.' + v.type);
- },
-
- async chooseType(e: MouseEvent, i: number) {
- const newValue = await this.showTypeMenu(e);
- this.theme[i] = [ this.theme[i][0], newValue ];
- },
-
- showTypeMenu(e: MouseEvent) {
- return new Promise<ThemeValue>((resolve) => {
- os.popupMenu([{
- text: this.$ts._theme.defaultValue,
- action: () => resolve(null),
- }, {
- text: this.$ts._theme.color,
- action: () => resolve('#000000'),
- }, {
- text: this.$ts._theme.func,
- action: () => resolve({
- type: 'func', name: 'alpha', arg: 1, value: 'accent'
- }),
- }, {
- text: this.$ts._theme.refProp,
- action: () => resolve({
- type: 'refProp', key: 'accent',
- }),
- }, {
- text: this.$ts._theme.refConst,
- action: () => resolve({
- type: 'refConst', key: '',
- }),
- }, {
- text: 'CSS',
- action: () => resolve({
- type: 'css', value: '',
- }),
- }], e.currentTarget || e.target);
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.t9makv94 {
- > ._section {
- > ._content {
- > .list-view {
- > .item {
- min-height: 48px;
- word-break: break-all;
-
- &:not(:last-child) {
- margin-bottom: 8px;
- }
-
- .select {
- margin: 24px 0;
- }
-
- .type {
- cursor: pointer;
- }
-
- .default-value {
- opacity: 0.6;
- pointer-events: none;
- user-select: none;
- }
-
- .color {
- > input {
- display: inline-block;
- width: 1.5em;
- height: 1.5em;
- }
-
- > div {
- margin-left: 8px;
- display: inline-block;
- }
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/pages/announcements.vue b/packages/client/src/pages/announcements.vue
index ca94640dda..53727823a4 100644
--- a/packages/client/src/pages/announcements.vue
+++ b/packages/client/src/pages/announcements.vue
@@ -36,7 +36,7 @@ export default defineComponent({
bg: 'var(--bg)',
},
pagination: {
- endpoint: 'announcements',
+ endpoint: 'announcements' as const,
limit: 10,
},
};
diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue
index 67ab2d8981..c9a8f36844 100644
--- a/packages/client/src/pages/channel.vue
+++ b/packages/client/src/pages/channel.vue
@@ -67,11 +67,11 @@ export default defineComponent({
channel: null,
showBanner: true,
pagination: {
- endpoint: 'channels/timeline',
+ endpoint: 'channels/timeline' as const,
limit: 10,
- params: () => ({
+ params: computed(() => ({
channelId: this.channelId,
- })
+ }))
},
};
},
diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue
index 48877ab3ec..4e538a6da3 100644
--- a/packages/client/src/pages/channels.vue
+++ b/packages/client/src/pages/channels.vue
@@ -60,15 +60,15 @@ export default defineComponent({
})),
tab: 'featured',
featuredPagination: {
- endpoint: 'channels/featured',
+ endpoint: 'channels/featured' as const,
noPaging: true,
},
followingPagination: {
- endpoint: 'channels/followed',
+ endpoint: 'channels/followed' as const,
limit: 5,
},
ownedPagination: {
- endpoint: 'channels/owned',
+ endpoint: 'channels/owned' as const,
limit: 5,
},
};
diff --git a/packages/client/src/pages/clip.vue b/packages/client/src/pages/clip.vue
index 077a6ac8b5..6b49221d32 100644
--- a/packages/client/src/pages/clip.vue
+++ b/packages/client/src/pages/clip.vue
@@ -50,11 +50,11 @@ export default defineComponent({
} : null),
clip: null,
pagination: {
- endpoint: 'clips/notes',
+ endpoint: 'clips/notes' as const,
limit: 10,
- params: () => ({
+ params: computed(() => ({
clipId: this.clipId,
- })
+ }))
},
};
},
diff --git a/packages/client/src/pages/drive.vue b/packages/client/src/pages/drive.vue
index f30000367f..1e17bea0cc 100644
--- a/packages/client/src/pages/drive.vue
+++ b/packages/client/src/pages/drive.vue
@@ -4,27 +4,21 @@
</div>
</template>
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
import XDrive from '@/components/drive.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- XDrive
- },
+let folder = $ref(null);
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: computed(() => this.folder ? this.folder.name : this.$ts.drive),
- icon: 'fas fa-cloud',
- bg: 'var(--bg)',
- hideHeader: true,
- },
- folder: null,
- };
- },
+defineExpose({
+ [symbols.PAGE_INFO]: computed(() => ({
+ title: folder ? folder.name : i18n.locale.drive,
+ icon: 'fas fa-cloud',
+ bg: 'var(--bg)',
+ hideHeader: true,
+ })),
});
</script>
diff --git a/packages/client/src/pages/emojis.emoji.vue b/packages/client/src/pages/emojis.emoji.vue
index 5dab72daea..83539ce7a3 100644
--- a/packages/client/src/pages/emojis.emoji.vue
+++ b/packages/client/src/pages/emojis.emoji.vue
@@ -8,35 +8,29 @@
</button>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
+import { i18n } from '@/i18n';
-export default defineComponent({
- props: {
- emoji: {
- type: Object,
- required: true,
- }
- },
+const props = defineProps<{
+ emoji: Record<string, unknown>; // TODO
+}>();
- methods: {
- menu(ev) {
- os.popupMenu([{
- type: 'label',
- text: ':' + this.emoji.name + ':',
- }, {
- text: this.$ts.copy,
- icon: 'fas fa-copy',
- action: () => {
- copyToClipboard(`:${this.emoji.name}:`);
- os.success();
- }
- }], ev.currentTarget || ev.target);
+function menu(ev) {
+ os.popupMenu([{
+ type: 'label',
+ text: ':' + props.emoji.name + ':',
+ }, {
+ text: i18n.locale.copy,
+ icon: 'fas fa-copy',
+ action: () => {
+ copyToClipboard(`:${props.emoji.name}:`);
+ os.success();
}
- }
-});
+ }], ev.currentTarget || ev.target);
+}
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue
index 2adb5345e2..6577f5abd9 100644
--- a/packages/client/src/pages/emojis.vue
+++ b/packages/client/src/pages/emojis.vue
@@ -4,55 +4,47 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent, computed } from 'vue';
+<script lang="ts" setup>
+import { ref, computed } from 'vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import XCategory from './emojis.category.vue';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- XCategory,
- },
+const tab = ref('category');
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => ({
- title: this.$ts.customEmojis,
- icon: 'fas fa-laugh',
- bg: 'var(--bg)',
- actions: [{
- icon: 'fas fa-ellipsis-h',
- handler: this.menu
- }],
- })),
- tab: 'category',
+function menu(ev) {
+ os.popupMenu([{
+ icon: 'fas fa-download',
+ text: i18n.locale.export,
+ action: async () => {
+ os.api('export-custom-emojis', {
+ })
+ .then(() => {
+ os.alert({
+ type: 'info',
+ text: i18n.locale.exportRequested,
+ });
+ }).catch((e) => {
+ os.alert({
+ type: 'error',
+ text: e.message,
+ });
+ });
}
- },
+ }], ev.currentTarget || ev.target);
+}
- methods: {
- menu(ev) {
- os.popupMenu([{
- icon: 'fas fa-download',
- text: this.$ts.export,
- action: async () => {
- os.api('export-custom-emojis', {
- })
- .then(() => {
- os.alert({
- type: 'info',
- text: this.$ts.exportRequested,
- });
- }).catch((e) => {
- os.alert({
- type: 'error',
- text: e.message,
- });
- });
- }
- }], ev.currentTarget || ev.target);
- }
- }
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.customEmojis,
+ icon: 'fas fa-laugh',
+ bg: 'var(--bg)',
+ actions: [{
+ icon: 'fas fa-ellipsis-h',
+ handler: menu,
+ }],
+ },
});
</script>
diff --git a/packages/client/src/pages/explore.vue b/packages/client/src/pages/explore.vue
index a3c3b771f2..04cc3662a7 100644
--- a/packages/client/src/pages/explore.vue
+++ b/packages/client/src/pages/explore.vue
@@ -156,7 +156,7 @@ export default defineComponent({
sort: '+createdAt',
} },
searchPagination: {
- endpoint: 'users/search',
+ endpoint: 'users/search' as const,
limit: 10,
params: computed(() => (this.searchQuery && this.searchQuery !== '') ? {
query: this.searchQuery,
@@ -178,7 +178,7 @@ export default defineComponent({
},
tagUsers(): any {
return {
- endpoint: 'hashtags/users',
+ endpoint: 'hashtags/users' as const,
limit: 30,
params: {
tag: this.tag,
diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue
index faab864744..8965b30d60 100644
--- a/packages/client/src/pages/favorites.vue
+++ b/packages/client/src/pages/favorites.vue
@@ -1,49 +1,49 @@
<template>
-<div class="jmelgwjh">
- <div class="body">
- <XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'"/>
- </div>
-</div>
+<MkSpacer :content-max="800">
+ <MkPagination ref="pagingComponent" :pagination="pagination">
+ <template #empty>
+ <div class="_fullinfo">
+ <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
+ <div>{{ $ts.noNotes }}</div>
+ </div>
+ </template>
+
+ <template #default="{ items }">
+ <XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false">
+ <XNote :key="item.id" :note="item.note" :class="$style.note"/>
+ </XList>
+ </template>
+ </MkPagination>
+</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XNotes from '@/components/notes.vue';
-import * as os from '@/os';
+<script lang="ts" setup>
+import { ref } from 'vue';
+import MkPagination from '@/components/ui/pagination.vue';
+import XNote from '@/components/note.vue';
+import XList from '@/components/date-separated-list.vue';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- XNotes
- },
+const pagination = {
+ endpoint: 'i/favorites' as const,
+ limit: 10,
+};
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.favorites,
- icon: 'fas fa-star',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'i/favorites',
- limit: 10,
- params: () => ({
- })
- },
- };
+const pagingComponent = ref<InstanceType<typeof MkPagination>>();
+
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.favorites,
+ icon: 'fas fa-star',
+ bg: 'var(--bg)',
},
});
</script>
-<style lang="scss" scoped>
-.jmelgwjh {
- background: var(--bg);
-
- > .body {
- box-sizing: border-box;
- max-width: 800px;
- margin: 0 auto;
- padding: 16px;
- }
+<style lang="scss" module>
+.note {
+ background: var(--panel);
+ border-radius: var(--radius);
}
</style>
diff --git a/packages/client/src/pages/featured.vue b/packages/client/src/pages/featured.vue
index 0844c0952f..725c70f0f7 100644
--- a/packages/client/src/pages/featured.vue
+++ b/packages/client/src/pages/featured.vue
@@ -4,29 +4,22 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- XNotes
- },
+const pagination = {
+ endpoint: 'notes/featured' as const,
+ limit: 10,
+ offsetMode: true,
+};
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.featured,
- icon: 'fas fa-fire-alt',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'notes/featured',
- limit: 10,
- offsetMode: true,
- },
- };
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.featured,
+ icon: 'fas fa-fire-alt',
+ bg: 'var(--bg)',
},
});
</script>
diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue
index 4e5f428ff9..6a4a28b6b4 100644
--- a/packages/client/src/pages/federation.vue
+++ b/packages/client/src/pages/federation.vue
@@ -6,7 +6,7 @@
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.host }}</template>
</MkInput>
- <div class="_inputSplit" style="margin-top: var(--margin);">
+ <FormSplit style="margin-top: var(--margin);">
<MkSelect v-model="state">
<template #label>{{ $ts.state }}</template>
<option value="all">{{ $ts.all }}</option>
@@ -38,7 +38,7 @@
<option value="+driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.descendingOrder }})</option>
<option value="-driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.ascendingOrder }})</option>
</MkSelect>
- </div>
+ </FormSplit>
</div>
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
@@ -95,75 +95,50 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
import MkPagination from '@/components/ui/pagination.vue';
+import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkPagination,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.federation,
- icon: 'fas fa-globe',
- bg: 'var(--bg)',
- },
- host: '',
- state: 'federating',
- sort: '+pubSub',
- pagination: {
- endpoint: 'federation/instances',
- limit: 10,
- offsetMode: true,
- params: () => ({
- sort: this.sort,
- host: this.host != '' ? this.host : null,
- ...(
- this.state === 'federating' ? { federating: true } :
- this.state === 'subscribing' ? { subscribing: true } :
- this.state === 'publishing' ? { publishing: true } :
- this.state === 'suspended' ? { suspended: true } :
- this.state === 'blocked' ? { blocked: true } :
- this.state === 'notResponding' ? { notResponding: true } :
- {})
- })
- },
- }
- },
+let host = $ref('');
+let state = $ref('federating');
+let sort = $ref('+pubSub');
+const pagination = {
+ endpoint: 'federation/instances' as const,
+ limit: 10,
+ offsetMode: true,
+ params: computed(() => ({
+ sort: sort,
+ host: host != '' ? host : null,
+ ...(
+ state === 'federating' ? { federating: true } :
+ state === 'subscribing' ? { subscribing: true } :
+ state === 'publishing' ? { publishing: true } :
+ state === 'suspended' ? { suspended: true } :
+ state === 'blocked' ? { blocked: true } :
+ state === 'notResponding' ? { notResponding: true } :
+ {})
+ }))
+};
- watch: {
- host() {
- this.$refs.instances.reload();
- },
- state() {
- this.$refs.instances.reload();
- }
- },
+function getStatus(instance) {
+ if (instance.isSuspended) return 'suspended';
+ if (instance.isNotResponding) return 'error';
+ return 'alive';
+};
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.federation,
+ icon: 'fas fa-globe',
+ bg: 'var(--bg)',
},
-
- methods: {
- getStatus(instance) {
- if (instance.isSuspended) return 'suspended';
- if (instance.isNotResponding) return 'error';
- return 'alive';
- },
- }
});
</script>
diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue
index 54d695091d..764daa0d3e 100644
--- a/packages/client/src/pages/follow-requests.vue
+++ b/packages/client/src/pages/follow-requests.vue
@@ -1,6 +1,6 @@
<template>
<div>
- <MkPagination ref="list" :pagination="pagination" class="mk-follow-requests">
+ <MkPagination ref="paginationComponent" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@@ -8,19 +8,21 @@
</div>
</template>
<template v-slot="{items}">
- <div v-for="req in items" :key="req.id" class="user _panel">
- <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/>
- <div class="body">
- <div class="name">
- <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA>
- <p class="acct">@{{ acct(req.follower) }}</p>
- </div>
- <div v-if="req.follower.description" class="description" :title="req.follower.description">
- <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/>
- </div>
- <div class="actions">
- <button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button>
- <button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button>
+ <div class="mk-follow-requests">
+ <div v-for="req in items" :key="req.id" class="user _panel">
+ <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/>
+ <div class="body">
+ <div class="name">
+ <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA>
+ <p class="acct">@{{ acct(req.follower) }}</p>
+ </div>
+ <div v-if="req.follower.description" class="description" :title="req.follower.description">
+ <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/>
+ </div>
+ <div class="actions">
+ <button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button>
+ <button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button>
+ </div>
</div>
</div>
</div>
@@ -29,45 +31,39 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref, computed } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import { userPage, acct } from '@/filters/user';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkPagination
- },
+const paginationComponent = ref<InstanceType<typeof MkPagination>>();
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.followRequests,
- icon: 'fas fa-user-clock',
- },
- pagination: {
- endpoint: 'following/requests/list',
- limit: 10,
- },
- };
- },
+const pagination = {
+ endpoint: 'following/requests/list' as const,
+ limit: 10,
+};
- methods: {
- accept(user) {
- os.api('following/requests/accept', { userId: user.id }).then(() => {
- this.$refs.list.reload();
- });
- },
- reject(user) {
- os.api('following/requests/reject', { userId: user.id }).then(() => {
- this.$refs.list.reload();
- });
- },
- userPage,
- acct
- }
+function accept(user) {
+ os.api('following/requests/accept', { userId: user.id }).then(() => {
+ paginationComponent.value.reload();
+ });
+}
+
+function reject(user) {
+ os.api('following/requests/reject', { userId: user.id }).then(() => {
+ paginationComponent.value.reload();
+ });
+}
+
+defineExpose({
+ [symbols.PAGE_INFO]: computed(() => ({
+ title: i18n.locale.followRequests,
+ icon: 'fas fa-user-clock',
+ bg: 'var(--bg)',
+ })),
});
</script>
diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue
index caca6aed4b..e3fa1a0fcd 100644
--- a/packages/client/src/pages/gallery/edit.vue
+++ b/packages/client/src/pages/gallery/edit.vue
@@ -1,16 +1,16 @@
<template>
-<FormBase>
+<div>
<FormSuspense :p="init">
<FormInput v-model="title">
- <span>{{ $ts.title }}</span>
+ <template #label>{{ $ts.title }}</template>
</FormInput>
<FormTextarea v-model="description" :max="500">
- <span>{{ $ts.description }}</span>
+ <template #label>{{ $ts.description }}</template>
</FormTextarea>
<FormGroup>
- <div v-for="file in files" :key="file.id" class="_debobigegoItem _debobigegoPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
+ <div v-for="file in files" :key="file.id" class="_formGroup wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
<div class="name">{{ file.name }}</div>
<button v-tooltip="$ts.remove" class="remove _button" @click="remove(file)"><i class="fas fa-times"></i></button>
</div>
@@ -24,19 +24,17 @@
<FormButton v-if="postId" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton>
</FormSuspense>
-</FormBase>
+</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormTuple from '@/components/debobigego/tuple.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormInput from '@/components/form/input.vue';
+import FormTextarea from '@/components/form/textarea.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormGroup from '@/components/form/group.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import { selectFiles } from '@/scripts/select-file';
import * as os from '@/os';
import * as symbols from '@/symbols';
@@ -47,7 +45,6 @@ export default defineComponent({
FormInput,
FormTextarea,
FormSwitch,
- FormBase,
FormGroup,
FormSuspense,
},
diff --git a/packages/client/src/pages/gallery/index.vue b/packages/client/src/pages/gallery/index.vue
index cd0d2a40e4..a19d69d5c2 100644
--- a/packages/client/src/pages/gallery/index.vue
+++ b/packages/client/src/pages/gallery/index.vue
@@ -81,19 +81,19 @@ export default defineComponent({
},
tab: 'explore',
recentPostsPagination: {
- endpoint: 'gallery/posts',
+ endpoint: 'gallery/posts' as const,
limit: 6,
},
popularPostsPagination: {
- endpoint: 'gallery/featured',
+ endpoint: 'gallery/featured' as const,
limit: 5,
},
myPostsPagination: {
- endpoint: 'i/gallery/posts',
+ endpoint: 'i/gallery/posts' as const,
limit: 5,
},
likedPostsPagination: {
- endpoint: 'i/gallery/likes',
+ endpoint: 'i/gallery/likes' as const,
limit: 5,
},
tags: [],
@@ -106,7 +106,7 @@ export default defineComponent({
},
tagUsers(): any {
return {
- endpoint: 'hashtags/users',
+ endpoint: 'hashtags/users' as const,
limit: 30,
params: {
tag: this.tag,
diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue
index 096947e6f8..1755c23286 100644
--- a/packages/client/src/pages/gallery/post.vue
+++ b/packages/client/src/pages/gallery/post.vue
@@ -1,6 +1,6 @@
<template>
<div class="_root">
- <transition name="fade" mode="out-in">
+ <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="post" class="rkxwuolj">
<div class="files">
<div v-for="file in post.files" :key="file.id" class="file">
@@ -93,11 +93,11 @@ export default defineComponent({
}]
} : null),
otherPostsPagination: {
- endpoint: 'users/gallery/posts',
+ endpoint: 'users/gallery/posts' as const,
limit: 6,
- params: () => ({
+ params: computed(() => ({
userId: this.post.user.id
- })
+ })),
},
post: null,
error: null,
diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue
index 85096d991a..fa36db0659 100644
--- a/packages/client/src/pages/instance-info.vue
+++ b/packages/client/src/pages/instance-info.vue
@@ -1,70 +1,71 @@
<template>
-<FormBase>
- <FormGroup v-if="instance">
- <template #label>{{ instance.host }}</template>
- <FormGroup>
- <div class="_debobigegoItem">
- <div class="_debobigegoPanel fnfelxur">
- <img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/>
- </div>
- </div>
- <FormKeyValueView>
- <template #key>Name</template>
- <template #value><span class="_monospace">{{ instance.name || `(${$ts.unknown})` }}</span></template>
- </FormKeyValueView>
- </FormGroup>
-
- <FormButton v-if="$i.isAdmin || $i.isModerator" primary @click="info">{{ $ts.settings }}</FormButton>
+<MkSpacer :content-max="600" :margin-min="16" :margin-max="32">
+ <div v-if="instance" class="_formRoot">
+ <div class="fnfelxur">
+ <img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/>
+ </div>
+ <MkKeyValue :copy="host" oneline style="margin: 1em 0;">
+ <template #key>Host</template>
+ <template #value><span class="_monospace"><MkLink :url="`https://${host}`">{{ host }}</MkLink></span></template>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
+ <template #key>Name</template>
+ <template #value>{{ instance.name || `(${$ts.unknown})` }}</template>
+ </MkKeyValue>
+ <MkKeyValue>
+ <template #key>{{ $ts.description }}</template>
+ <template #value>{{ instance.description }}</template>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
+ <template #key>{{ $ts.software }}</template>
+ <template #value><span class="_monospace">{{ instance.softwareName || `(${$ts.unknown})` }} / {{ instance.softwareVersion || `(${$ts.unknown})` }}</span></template>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
+ <template #key>{{ $ts.administrator }}</template>
+ <template #value>{{ instance.maintainerName || `(${$ts.unknown})` }} ({{ instance.maintainerEmail || `(${$ts.unknown})` }})</template>
+ </MkKeyValue>
- <FormTextarea readonly :value="instance.description">
- <span>{{ $ts.description }}</span>
- </FormTextarea>
+ <FormSection v-if="iAmModerator">
+ <template #label>Moderation</template>
+ <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.stopActivityDelivery }}</FormSwitch>
+ <FormSwitch v-model="isBlocked" class="_formBlock" @update:modelValue="toggleBlock">{{ $ts.blockThisInstance }}</FormSwitch>
+ </FormSection>
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts.software }}</template>
- <template #value><span class="_monospace">{{ instance.softwareName || `(${$ts.unknown})` }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.version }}</template>
- <template #value><span class="_monospace">{{ instance.softwareVersion || `(${$ts.unknown})` }}</span></template>
- </FormKeyValueView>
- </FormGroup>
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts.administrator }}</template>
- <template #value><span class="_monospace">{{ instance.maintainerName || `(${$ts.unknown})` }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.contact }}</template>
- <template #value><span class="_monospace">{{ instance.maintainerEmail || `(${$ts.unknown})` }}</span></template>
- </FormKeyValueView>
- </FormGroup>
- <FormGroup>
- <FormKeyValueView>
+ <FormSection>
+ <MkKeyValue oneline style="margin: 1em 0;">
+ <template #key>{{ $ts.registeredAt }}</template>
+ <template #value><MkTime mode="detail" :time="instance.caughtAt"/></template>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
+ <template #key>{{ $ts.updatedAt }}</template>
+ <template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.latestRequestSentAt }}</template>
<template #value><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.latestStatus }}</template>
<template #value>{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.latestRequestReceivedAt }}</template>
<template #value><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template>
- </FormKeyValueView>
- </FormGroup>
- <FormGroup>
- <FormKeyValueView>
+ </MkKeyValue>
+ </FormSection>
+
+ <FormSection>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>Open Registrations</template>
<template #value>{{ instance.openRegistrations ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- </FormGroup>
- <div class="_debobigegoItem">
- <div class="_debobigegoLabel">{{ $ts.statistics }}</div>
- <div class="_debobigegoPanel cmhjzshl">
+ </MkKeyValue>
+ </FormSection>
+
+ <FormSection>
+ <template #label>{{ $ts.statistics }}</template>
+ <div class="cmhjzshl">
<div class="selects">
- <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
+ <MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
<option value="instance-requests">{{ $ts._instanceCharts.requests }}</option>
<option value="instance-users">{{ $ts._instanceCharts.users }}</option>
<option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option>
@@ -83,147 +84,100 @@
</MkSelect>
</div>
<div class="chart">
- <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart>
+ <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
</div>
</div>
- </div>
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts.registeredAt }}</template>
- <template #value><MkTime mode="detail" :time="instance.caughtAt"/></template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.updatedAt }}</template>
- <template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
- </FormKeyValueView>
- </FormGroup>
- <FormObjectView tall :value="instance">
- <span>Raw</span>
- </FormObjectView>
- <FormGroup>
+ </FormSection>
+
+ <MkObjectView tall :value="instance">
+ </MkObjectView>
+
+ <FormSection>
<template #label>Well-known resources</template>
- <FormLink :to="`https://${host}/.well-known/host-meta`" external>host-meta</FormLink>
- <FormLink :to="`https://${host}/.well-known/host-meta.json`" external>host-meta.json</FormLink>
- <FormLink :to="`https://${host}/.well-known/nodeinfo`" external>nodeinfo</FormLink>
- <FormLink :to="`https://${host}/robots.txt`" external>robots.txt</FormLink>
- <FormLink :to="`https://${host}/manifest.json`" external>manifest.json</FormLink>
- </FormGroup>
- <FormSuspense v-slot="{ result: dns }" :p="dnsPromiseFactory">
- <FormGroup>
- <template #label>DNS</template>
- <FormKeyValueView v-for="record in dns.a" :key="record">
- <template #key>A</template>
- <template #value><span class="_monospace">{{ record }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView v-for="record in dns.aaaa" :key="record">
- <template #key>AAAA</template>
- <template #value><span class="_monospace">{{ record }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView v-for="record in dns.cname" :key="record">
- <template #key>CNAME</template>
- <template #value><span class="_monospace">{{ record }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView v-for="record in dns.txt">
- <template #key>TXT</template>
- <template #value><span class="_monospace">{{ record[0] }}</span></template>
- </FormKeyValueView>
- </FormGroup>
- </FormSuspense>
- </FormGroup>
-</FormBase>
+ <FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink>
+ <FormLink :to="`https://${host}/.well-known/host-meta.json`" external style="margin-bottom: 8px;">host-meta.json</FormLink>
+ <FormLink :to="`https://${host}/.well-known/nodeinfo`" external style="margin-bottom: 8px;">nodeinfo</FormLink>
+ <FormLink :to="`https://${host}/robots.txt`" external style="margin-bottom: 8px;">robots.txt</FormLink>
+ <FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
+ </FormSection>
+ </div>
+</MkSpacer>
</template>
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
+import * as misskey from 'misskey-js';
import MkChart from '@/components/chart.vue';
-import FormObjectView from '@/components/debobigego/object-view.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import MkObjectView from '@/components/object-view.vue';
+import FormLink from '@/components/form/link.vue';
+import MkLink from '@/components/link.vue';
+import FormSection from '@/components/form/section.vue';
+import MkKeyValue from '@/components/key-value.vue';
import MkSelect from '@/components/form/select.vue';
+import FormSwitch from '@/components/form/switch.vue';
import * as os from '@/os';
import number from '@/filters/number';
import bytes from '@/filters/bytes';
import * as symbols from '@/symbols';
-import MkInstanceInfo from '@/pages/admin/instance.vue';
+import { iAmModerator } from '@/account';
-export default defineComponent({
- components: {
- FormBase,
- FormTextarea,
- FormObjectView,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- FormSuspense,
- MkSelect,
- MkChart,
- },
+const props = defineProps<{
+ host: string;
+}>();
- props: {
- host: {
- type: String,
- required: true
- }
- },
+let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null);
+let instance = $ref<misskey.entities.Instance | null>(null);
+let suspended = $ref(false);
+let isBlocked = $ref(false);
+let chartSrc = $ref('instance-requests');
+let chartSpan = $ref('hour');
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.instanceInfo,
- icon: 'fas fa-info-circle',
- actions: [{
- text: `https://${this.host}`,
- icon: 'fas fa-external-link-alt',
- handler: () => {
- window.open(`https://${this.host}`, '_blank');
- }
- }],
- },
- instance: null,
- dnsPromiseFactory: () => os.api('federation/dns', {
- host: this.host
- }),
- chartSrc: 'instance-requests',
- chartSpan: 'hour',
- }
- },
+async function fetch() {
+ meta = await os.api('meta', { detail: true });
+ instance = await os.api('federation/show-instance', {
+ host: props.host,
+ });
+ suspended = instance.isSuspended;
+ isBlocked = meta.blockedHosts.includes(instance.host);
+}
- mounted() {
- this.fetch();
- },
+async function toggleBlock(ev) {
+ if (meta == null) return;
+ await os.api('admin/update-meta', {
+ blockedHosts: isBlocked ? meta.blockedHosts.concat([instance.host]) : meta.blockedHosts.filter(x => x !== instance.host)
+ });
+}
- methods: {
- number,
- bytes,
+async function toggleSuspend(v) {
+ await os.api('admin/federation/update-instance', {
+ host: instance.host,
+ isSuspended: suspended,
+ });
+}
- async fetch() {
- this.instance = await os.api('federation/show-instance', {
- host: this.host
- });
- },
+fetch();
- info() {
- os.popup(MkInstanceInfo, {
- instance: this.instance
- }, {}, 'closed');
- }
- }
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: props.host,
+ icon: 'fas fa-info-circle',
+ bg: 'var(--bg)',
+ actions: [{
+ text: `https://${props.host}`,
+ icon: 'fas fa-external-link-alt',
+ handler: () => {
+ window.open(`https://${props.host}`, '_blank');
+ }
+ }],
+ },
});
</script>
<style lang="scss" scoped>
.fnfelxur {
- padding: 16px;
-
> .icon {
display: block;
- margin: auto;
+ margin: 0;
height: 64px;
border-radius: 8px;
}
@@ -232,7 +186,7 @@ export default defineComponent({
.cmhjzshl {
> .selects {
display: flex;
- padding: 16px;
+ margin: 0 0 16px 0;
}
}
</style>
diff --git a/packages/client/src/pages/mentions.vue b/packages/client/src/pages/mentions.vue
index 691d3bd9aa..bda56fc729 100644
--- a/packages/client/src/pages/mentions.vue
+++ b/packages/client/src/pages/mentions.vue
@@ -4,28 +4,21 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- XNotes
- },
+const pagination = {
+ endpoint: 'notes/mentions' as const,
+ limit: 10,
+};
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.mentions,
- icon: 'fas fa-at',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'notes/mentions',
- limit: 10,
- },
- };
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.mentions,
+ icon: 'fas fa-at',
+ bg: 'var(--bg)',
},
});
</script>
diff --git a/packages/client/src/pages/messages.vue b/packages/client/src/pages/messages.vue
index 9085af9489..8efdc55586 100644
--- a/packages/client/src/pages/messages.vue
+++ b/packages/client/src/pages/messages.vue
@@ -4,31 +4,24 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- XNotes
- },
+const pagination = {
+ endpoint: 'notes/mentions' as const,
+ limit: 10,
+ params: () => ({
+ visibility: 'specified'
+ }),
+};
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.directNotes,
- icon: 'fas fa-envelope',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'notes/mentions',
- limit: 10,
- params: () => ({
- visibility: 'specified'
- })
- },
- };
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.directNotes,
+ icon: 'fas fa-envelope',
+ bg: 'var(--bg)',
},
});
</script>
diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue
index 01f9d4518f..554ebc4b6b 100644
--- a/packages/client/src/pages/messaging/index.vue
+++ b/packages/client/src/pages/messaging/index.vue
@@ -44,6 +44,7 @@ import * as Acct from 'misskey-js/built/acct';
import MkButton from '@/components/ui/button.vue';
import { acct } from '@/filters/user';
import * as os from '@/os';
+import { stream } from '@/stream';
import * as symbols from '@/symbols';
export default defineComponent({
@@ -66,7 +67,7 @@ export default defineComponent({
},
mounted() {
- this.connection = markRaw(os.stream.useChannel('messagingIndex'));
+ this.connection = markRaw(stream.useChannel('messagingIndex'));
this.connection.on('message', this.onMessage);
this.connection.on('read', this.onRead);
diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue
index 8d92c430f1..0fc7c8a5df 100644
--- a/packages/client/src/pages/messaging/messaging-room.form.vue
+++ b/packages/client/src/pages/messaging/messaging-room.form.vue
@@ -7,7 +7,7 @@
ref="text"
v-model="text"
:placeholder="$ts.inputMessageHere"
- @keypress="onKeypress"
+ @keydown="onKeydown"
@compositionupdate="onCompositionUpdate"
@paste="onPaste"
></textarea>
@@ -28,6 +28,7 @@ import * as autosize from 'autosize';
import { formatTimeString } from '@/scripts/format-time-string';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
+import { stream } from '@/stream';
import { Autocomplete } from '@/scripts/autocomplete';
import { throttle } from 'throttle-debounce';
@@ -48,7 +49,7 @@ export default defineComponent({
file: null,
sending: false,
typing: throttle(3000, () => {
- os.stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id });
+ stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id });
}),
};
},
@@ -140,7 +141,7 @@ export default defineComponent({
//#endregion
},
- onKeypress(e) {
+ onKeydown(e) {
this.typing();
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) {
this.send();
diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue
index ffc7f7bc0d..a715dad6de 100644
--- a/packages/client/src/pages/messaging/messaging-room.vue
+++ b/packages/client/src/pages/messaging/messaging-room.vue
@@ -24,7 +24,7 @@
</I18n>
<MkEllipsis/>
</div>
- <transition name="fade">
+ <transition :name="$store.state.animation ? 'fade' : ''">
<div v-show="showIndicator" class="new-message">
<button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas fa-arrow-circle-down"></i>{{ $ts.newMessageExists }}</button>
</div>
@@ -43,6 +43,7 @@ import XForm from './messaging-room.form.vue';
import * as Acct from 'misskey-js/built/acct';
import { isBottom, onScrollBottom, scroll } from '@/scripts/scroll';
import * as os from '@/os';
+import { stream } from '@/stream';
import { popout } from '@/scripts/popout';
import * as sound from '@/scripts/sound';
import * as symbols from '@/symbols';
@@ -141,7 +142,7 @@ const Component = defineComponent({
this.group = group;
}
- this.connection = markRaw(os.stream.useChannel('messaging', {
+ this.connection = markRaw(stream.useChannel('messaging', {
otherparty: this.user ? this.user.id : undefined,
group: this.group ? this.group.id : undefined,
}));
@@ -161,7 +162,7 @@ const Component = defineComponent({
// もっと見るの交差検知を発火させないためにfetchは
// スクロールが終わるまでfalseにしておく
// scrollendのようなイベントはないのでsetTimeoutで
- setTimeout(() => this.fetching = false, 300);
+ window.setTimeout(() => this.fetching = false, 300);
});
},
@@ -299,9 +300,9 @@ const Component = defineComponent({
this.showIndicator = false;
});
- if (this.timer) clearTimeout(this.timer);
+ if (this.timer) window.clearTimeout(this.timer);
- this.timer = setTimeout(() => {
+ this.timer = window.setTimeout(() => {
this.showIndicator = false;
}, 4000);
},
diff --git a/packages/client/src/pages/my-antennas/create.vue b/packages/client/src/pages/my-antennas/create.vue
index 173807475a..427c9935c3 100644
--- a/packages/client/src/pages/my-antennas/create.vue
+++ b/packages/client/src/pages/my-antennas/create.vue
@@ -4,45 +4,37 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+<script lang="ts" setup>
+import { } from 'vue';
import XAntenna from './editor.vue';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
+import { router } from '@/router';
-export default defineComponent({
- components: {
- MkButton,
- XAntenna,
- },
+let draft = $ref({
+ name: '',
+ src: 'all',
+ userListId: null,
+ userGroupId: null,
+ users: [],
+ keywords: [],
+ excludeKeywords: [],
+ withReplies: false,
+ caseSensitive: false,
+ withFile: false,
+ notify: false
+});
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.manageAntennas,
- icon: 'fas fa-satellite',
- },
- draft: {
- name: '',
- src: 'all',
- userListId: null,
- userGroupId: null,
- users: [],
- keywords: [],
- excludeKeywords: [],
- withReplies: false,
- caseSensitive: false,
- withFile: false,
- notify: false
- },
- };
- },
+function onAntennaCreated() {
+ router.push('/my/antennas');
+}
- methods: {
- onAntennaCreated() {
- this.$router.push('/my/antennas');
- },
- }
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.manageAntennas,
+ icon: 'fas fa-satellite',
+ bg: 'var(--bg)',
+ },
});
</script>
diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue
index d185e796c3..7138d269a9 100644
--- a/packages/client/src/pages/my-antennas/index.vue
+++ b/packages/client/src/pages/my-antennas/index.vue
@@ -38,7 +38,7 @@ export default defineComponent({
}
},
pagination: {
- endpoint: 'antennas/list',
+ endpoint: 'antennas/list' as const,
limit: 10,
},
};
diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue
index a5bbc3fd2d..97b563f6f8 100644
--- a/packages/client/src/pages/my-clips/index.vue
+++ b/packages/client/src/pages/my-clips/index.vue
@@ -3,7 +3,7 @@
<div class="qtcaoidl">
<MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
- <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="list">
+ <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="list">
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
<b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div>
@@ -13,71 +13,64 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import MkButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import i18n from '@/components/global/i18n';
-export default defineComponent({
- components: {
- MkPagination,
- MkButton,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.clip,
- icon: 'fas fa-paperclip',
- bg: 'var(--bg)',
- action: {
- icon: 'fas fa-plus',
- handler: this.create
- }
- },
- pagination: {
- endpoint: 'clips/list',
- limit: 10,
- },
- draft: null,
- };
- },
+const pagination = {
+ endpoint: 'clips/list' as const,
+ limit: 10,
+};
- methods: {
- async create() {
- const { canceled, result } = await os.form(this.$ts.createNewClip, {
- name: {
- type: 'string',
- label: this.$ts.name
- },
- description: {
- type: 'string',
- required: false,
- multiline: true,
- label: this.$ts.description
- },
- isPublic: {
- type: 'boolean',
- label: this.$ts.public,
- default: false
- }
- });
- if (canceled) return;
+const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
- os.apiWithDialog('clips/create', result);
+async function create() {
+ const { canceled, result } = await os.form(i18n.locale.createNewClip, {
+ name: {
+ type: 'string',
+ label: i18n.locale.name,
},
-
- onClipCreated() {
- this.$refs.list.reload();
- this.draft = null;
+ description: {
+ type: 'string',
+ required: false,
+ multiline: true,
+ label: i18n.locale.description,
},
+ isPublic: {
+ type: 'boolean',
+ label: i18n.locale.public,
+ default: false,
+ },
+ });
+ if (canceled) return;
+
+ os.apiWithDialog('clips/create', result);
+
+ pagingComponent.reload();
+}
+
+function onClipCreated() {
+ pagingComponent.reload();
+}
- onClipDeleted() {
- this.$refs.list.reload();
+function onClipDeleted() {
+ pagingComponent.reload();
+}
+
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.clip,
+ icon: 'fas fa-paperclip',
+ bg: 'var(--bg)',
+ action: {
+ icon: 'fas fa-plus',
+ handler: create
},
- }
+ },
});
</script>
diff --git a/packages/client/src/pages/my-groups/group.vue b/packages/client/src/pages/my-groups/group.vue
index c307f037a6..92c0483af9 100644
--- a/packages/client/src/pages/my-groups/group.vue
+++ b/packages/client/src/pages/my-groups/group.vue
@@ -1,6 +1,6 @@
<template>
<div class="mk-group-page">
- <transition name="zoom" mode="out-in">
+ <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<div v-if="group" class="_section">
<div class="_content" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
<MkButton inline @click="invite()">{{ $ts.invite }}</MkButton>
@@ -11,7 +11,7 @@
</div>
</transition>
- <transition name="zoom" mode="out-in">
+ <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<div v-if="group" class="_section members _gap">
<div class="_title">{{ $ts.members }}</div>
<div class="_content">
diff --git a/packages/client/src/pages/my-groups/index.vue b/packages/client/src/pages/my-groups/index.vue
index db5ccde466..4b2b2963a8 100644
--- a/packages/client/src/pages/my-groups/index.vue
+++ b/packages/client/src/pages/my-groups/index.vue
@@ -87,15 +87,15 @@ export default defineComponent({
})),
tab: 'owned',
ownedPagination: {
- endpoint: 'users/groups/owned',
+ endpoint: 'users/groups/owned' as const,
limit: 10,
},
joinedPagination: {
- endpoint: 'users/groups/joined',
+ endpoint: 'users/groups/joined' as const,
limit: 10,
},
invitationPagination: {
- endpoint: 'i/user-group-invites',
+ endpoint: 'i/user-group-invites' as const,
limit: 10,
},
};
diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue
index 94a869b9ff..e6fcba1b34 100644
--- a/packages/client/src/pages/my-lists/index.vue
+++ b/packages/client/src/pages/my-lists/index.vue
@@ -3,7 +3,7 @@
<div class="qkcjvfiv">
<MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton>
- <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="lists _content">
+ <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists _content">
<MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`">
<div class="name">{{ list.name }}</div>
<MkAvatars :user-ids="list.userIds"/>
@@ -13,50 +13,41 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import MkButton from '@/components/ui/button.vue';
import MkAvatars from '@/components/avatars.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkPagination,
- MkButton,
- MkAvatars,
- },
+const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.manageLists,
- icon: 'fas fa-list-ul',
- bg: 'var(--bg)',
- action: {
- icon: 'fas fa-plus',
- handler: this.create
- },
- },
- pagination: {
- endpoint: 'users/lists/list',
- limit: 10,
- },
- };
- },
+const pagination = {
+ endpoint: 'users/lists/list' as const,
+ limit: 10,
+};
+
+async function create() {
+ const { canceled, result: name } = await os.inputText({
+ title: i18n.locale.enterListName,
+ });
+ if (canceled) return;
+ await os.apiWithDialog('users/lists/create', { name: name });
+ pagingComponent.reload();
+}
- methods: {
- async create() {
- const { canceled, result: name } = await os.inputText({
- title: this.$ts.enterListName,
- });
- if (canceled) return;
- await os.api('users/lists/create', { name: name });
- this.$refs.list.reload();
- os.success();
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.manageLists,
+ icon: 'fas fa-list-ul',
+ bg: 'var(--bg)',
+ action: {
+ icon: 'fas fa-plus',
+ handler: create,
},
- }
+ },
});
</script>
diff --git a/packages/client/src/pages/my-lists/list.vue b/packages/client/src/pages/my-lists/list.vue
index a25522f933..bc24f58431 100644
--- a/packages/client/src/pages/my-lists/list.vue
+++ b/packages/client/src/pages/my-lists/list.vue
@@ -1,7 +1,7 @@
<template>
<MkSpacer :content-max="700">
<div class="mk-list-page">
- <transition name="zoom" mode="out-in">
+ <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<div v-if="list" class="_section">
<div class="_content">
<MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton>
@@ -11,7 +11,7 @@
</div>
</transition>
- <transition name="zoom" mode="out-in">
+ <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<div v-if="list" class="_section members _gap">
<div class="_title">{{ $ts.members }}</div>
<div class="_content">
diff --git a/packages/client/src/pages/not-found.vue b/packages/client/src/pages/not-found.vue
index 92d3f399f7..914fdb9297 100644
--- a/packages/client/src/pages/not-found.vue
+++ b/packages/client/src/pages/not-found.vue
@@ -7,19 +7,15 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@/os';
+<script lang="ts" setup>
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.notFound,
- icon: 'fas fa-exclamation-triangle'
- },
- }
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.notFound,
+ icon: 'fas fa-exclamation-triangle',
+ bg: 'var(--bg)',
},
});
</script>
diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue
index d40082381c..efeea345dc 100644
--- a/packages/client/src/pages/note.vue
+++ b/packages/client/src/pages/note.vue
@@ -1,7 +1,7 @@
<template>
<MkSpacer :content-max="800">
<div class="fcuexfpr">
- <transition name="fade" mode="out-in">
+ <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="note" class="note">
<div v-if="showNext" class="_gap">
<XNotes class="_content" :pagination="next" :no-gap="true"/>
@@ -82,21 +82,21 @@ export default defineComponent({
showNext: false,
error: null,
prev: {
- endpoint: 'users/notes',
+ endpoint: 'users/notes' as const,
limit: 10,
- params: init => ({
+ params: computed(() => ({
userId: this.note.userId,
untilId: this.note.id,
- })
+ })),
},
next: {
reversed: true,
- endpoint: 'users/notes',
+ endpoint: 'users/notes' as const,
limit: 10,
- params: init => ({
+ params: computed(() => ({
userId: this.note.userId,
sinceId: this.note.id,
- })
+ })),
},
};
},
diff --git a/packages/client/src/pages/notifications.vue b/packages/client/src/pages/notifications.vue
index 695c54a535..090e80f99a 100644
--- a/packages/client/src/pages/notifications.vue
+++ b/packages/client/src/pages/notifications.vue
@@ -6,70 +6,62 @@
</MkSpacer>
</template>
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
import XNotifications from '@/components/notifications.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { notificationTypes } from 'misskey-js';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- XNotifications
- },
+let tab = $ref('all');
+let includeTypes = $ref<string[] | null>(null);
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => ({
- title: this.$ts.notifications,
- icon: 'fas fa-bell',
- bg: 'var(--bg)',
- actions: [{
- text: this.$ts.filter,
- icon: 'fas fa-filter',
- highlighted: this.includeTypes != null,
- handler: this.setFilter,
- }, {
- text: this.$ts.markAllAsRead,
- icon: 'fas fa-check',
- handler: () => {
- os.apiWithDialog('notifications/mark-all-as-read');
- },
- }],
- tabs: [{
- active: this.tab === 'all',
- title: this.$ts.all,
- onClick: () => { this.tab = 'all'; },
- }, {
- active: this.tab === 'unread',
- title: this.$ts.unread,
- onClick: () => { this.tab = 'unread'; },
- },]
- })),
- tab: 'all',
- includeTypes: null,
- };
- },
-
- methods: {
- setFilter(ev) {
- const typeItems = notificationTypes.map(t => ({
- text: this.$t(`_notification._types.${t}`),
- active: this.includeTypes && this.includeTypes.includes(t),
- action: () => {
- this.includeTypes = [t];
- }
- }));
- const items = this.includeTypes != null ? [{
- icon: 'fas fa-times',
- text: this.$ts.clear,
- action: () => {
- this.includeTypes = null;
- }
- }, null, ...typeItems] : typeItems;
- os.popupMenu(items, ev.currentTarget || ev.target);
+function setFilter(ev) {
+ const typeItems = notificationTypes.map(t => ({
+ text: i18n.t(`_notification._types.${t}`),
+ active: includeTypes && includeTypes.includes(t),
+ action: () => {
+ includeTypes = [t];
+ }
+ }));
+ const items = includeTypes != null ? [{
+ icon: 'fas fa-times',
+ text: i18n.locale.clear,
+ action: () => {
+ includeTypes = null;
}
- }
+ }, null, ...typeItems] : typeItems;
+ os.popupMenu(items, ev.currentTarget || ev.target);
+}
+
+defineExpose({
+ [symbols.PAGE_INFO]: computed(() => ({
+ title: i18n.locale.notifications,
+ icon: 'fas fa-bell',
+ bg: 'var(--bg)',
+ actions: [{
+ text: i18n.locale.filter,
+ icon: 'fas fa-filter',
+ highlighted: includeTypes != null,
+ handler: setFilter,
+ }, {
+ text: i18n.locale.markAllAsRead,
+ icon: 'fas fa-check',
+ handler: () => {
+ os.apiWithDialog('notifications/mark-all-as-read');
+ },
+ }],
+ tabs: [{
+ active: tab === 'all',
+ title: i18n.locale.all,
+ onClick: () => { tab = 'all'; },
+ }, {
+ active: tab === 'unread',
+ title: i18n.locale.unread,
+ onClick: () => { tab = 'unread'; },
+ },]
+ })),
});
</script>
diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue
index 3a4803c3a3..b2c039a269 100644
--- a/packages/client/src/pages/page.vue
+++ b/packages/client/src/pages/page.vue
@@ -1,6 +1,6 @@
<template>
<MkSpacer :content-max="700">
- <transition name="fade" mode="out-in">
+ <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="page" :key="page.id" v-size="{ max: [450] }" class="xcukqgmh">
<div class="_block main">
<!--
@@ -106,11 +106,11 @@ export default defineComponent({
page: null,
error: null,
otherPostsPagination: {
- endpoint: 'users/pages',
+ endpoint: 'users/pages' as const,
limit: 6,
- params: () => ({
+ params: computed(() => ({
userId: this.page.user.id
- })
+ })),
},
};
},
diff --git a/packages/client/src/pages/pages.vue b/packages/client/src/pages/pages.vue
index f1dd64f119..dcccf7f7c4 100644
--- a/packages/client/src/pages/pages.vue
+++ b/packages/client/src/pages/pages.vue
@@ -62,15 +62,15 @@ export default defineComponent({
})),
tab: 'featured',
featuredPagesPagination: {
- endpoint: 'pages/featured',
+ endpoint: 'pages/featured' as const,
noPaging: true,
},
myPagesPagination: {
- endpoint: 'i/pages',
+ endpoint: 'i/pages' as const,
limit: 5,
},
likedPagesPagination: {
- endpoint: 'i/page-likes',
+ endpoint: 'i/page-likes' as const,
limit: 5,
},
};
diff --git a/packages/client/src/pages/preview.vue b/packages/client/src/pages/preview.vue
index 9d1ebb74ed..8eb4549516 100644
--- a/packages/client/src/pages/preview.vue
+++ b/packages/client/src/pages/preview.vue
@@ -4,24 +4,18 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
import MkSample from '@/components/sample.vue';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkSample,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.preview,
- icon: 'fas fa-eye',
- },
- }
- },
+defineExpose({
+ [symbols.PAGE_INFO]: computed(() => ({
+ title: i18n.locale.preview,
+ icon: 'fas fa-eye',
+ bg: 'var(--bg)',
+ })),
});
</script>
diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue
index f9a2500840..8ef73858f6 100644
--- a/packages/client/src/pages/reset-password.vue
+++ b/packages/client/src/pages/reset-password.vue
@@ -1,67 +1,53 @@
<template>
-<FormBase v-if="token">
- <FormInput v-model="password" type="password">
- <template #prefix><i class="fas fa-lock"></i></template>
- <span>{{ $ts.newPassword }}</span>
- </FormInput>
-
- <FormButton primary @click="save">{{ $ts.save }}</FormButton>
-</FormBase>
+<MkSpacer v-if="token" :content-max="700" :margin-min="16" :margin-max="32">
+ <div class="_formRoot">
+ <FormInput v-model="password" type="password" class="_formBlock">
+ <template #prefix><i class="fas fa-lock"></i></template>
+ <template #label>{{ i18n.locale.newPassword }}</template>
+ </FormInput>
+
+ <FormButton primary class="_formBlock" @click="save">{{ i18n.locale.save }}</FormButton>
+ </div>
+</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormButton from '@/components/debobigego/button.vue';
+<script lang="ts" setup>
+import { onMounted } from 'vue';
+import FormInput from '@/components/form/input.vue';
+import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
+import { router } from '@/router';
-export default defineComponent({
- components: {
- FormBase,
- FormGroup,
- FormLink,
- FormInput,
- FormButton,
- },
-
- props: {
- token: {
- type: String,
- required: false
- }
- },
+const props = defineProps<{
+ token?: string;
+}>();
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.resetPassword,
- icon: 'fas fa-lock'
- },
- password: '',
- }
- },
+let password = $ref('');
- mounted() {
- if (this.token == null) {
- os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed');
- this.$router.push('/');
- }
- },
+async function save() {
+ await os.apiWithDialog('reset-password', {
+ token: props.token,
+ password: password,
+ });
+ router.push('/');
+}
- methods: {
- async save() {
- await os.apiWithDialog('reset-password', {
- token: this.token,
- password: this.password,
- });
- this.$router.push('/');
- }
+onMounted(() => {
+ if (props.token == null) {
+ os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed');
+ router.push('/');
}
});
+
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.resetPassword,
+ icon: 'fas fa-lock',
+ bg: 'var(--bg)',
+ },
+});
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/pages/reversi/game.board.vue b/packages/client/src/pages/reversi/game.board.vue
deleted file mode 100644
index eb6fef2799..0000000000
--- a/packages/client/src/pages/reversi/game.board.vue
+++ /dev/null
@@ -1,528 +0,0 @@
-<template>
-<div class="xqnhankfuuilcwvhgsopeqncafzsquya">
- <header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ $ts._reversi.black }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ $ts._reversi.white }})</header>
-
- <div style="overflow: hidden; line-height: 28px;">
- <p v-if="!iAmPlayer && !game.isEnded" class="turn">
- <Mfm :key="'turn:' + turnUser().name" :text="$t('_reversi.turnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
- <MkEllipsis/>
- </p>
- <p v-if="logPos != logs.length" class="turn">
- <Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
- </p>
- <p v-if="iAmPlayer && !game.isEnded && !isMyTurn()" class="turn1">{{ $ts._reversi.opponentTurn }}<MkEllipsis/></p>
- <p v-if="iAmPlayer && !game.isEnded && isMyTurn()" class="turn2" style="animation: tada 1s linear infinite both;">{{ $ts._reversi.myTurn }}</p>
- <p v-if="game.isEnded && logPos == logs.length" class="result">
- <template v-if="game.winner">
- <Mfm :key="'won'" :text="$t('_reversi.won', { name: game.winner.name })" :plain="true" :custom-emojis="game.winner.emojis"/>
- <span v-if="game.surrendered != null"> ({{ $ts._reversi.surrendered }})</span>
- </template>
- <template v-else>{{ $ts._reversi.drawn }}</template>
- </p>
- </div>
-
- <div class="board">
- <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x">
- <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
- </div>
- <div class="flex">
- <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y">
- <div v-for="i in game.map.length">{{ i }}</div>
- </div>
- <div class="cells" :style="cellsStyle">
- <div v-for="(stone, i) in o.board"
- :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn(), can: turnUser() ? o.canPut(turnUser().id == blackUser.id, i) : null, prev: o.prevPos == i }"
- :title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`"
- @click="set(i)"
- >
- <template v-if="$store.state.gamesReversiUseAvatarStones || true">
- <img v-if="stone === true" :src="blackUser.avatarUrl" alt="black">
- <img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white">
- </template>
- <template v-else>
- <i v-if="stone === true" class="fas fa-circle"></i>
- <i v-if="stone === false" class="far fa-circle"></i>
- </template>
- </div>
- </div>
- <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y">
- <div v-for="i in game.map.length">{{ i }}</div>
- </div>
- </div>
- <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x">
- <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
- </div>
- </div>
-
- <p class="status"><b>{{ $t('_reversi.turnCount', { count: logPos }) }}</b> {{ $ts._reversi.black }}:{{ o.blackCount }} {{ $ts._reversi.white }}:{{ o.whiteCount }} {{ $ts._reversi.total }}:{{ o.blackCount + o.whiteCount }}</p>
-
- <div v-if="!game.isEnded && iAmPlayer" class="actions">
- <MkButton inline @click="surrender">{{ $ts._reversi.surrender }}</MkButton>
- </div>
-
- <div v-if="game.isEnded" class="player">
- <span>{{ logPos }} / {{ logs.length }}</span>
- <div v-if="!autoplaying" class="buttons">
- <MkButton inline :disabled="logPos == 0" @click="logPos = 0"><i class="fas fa-angle-double-left"></i></MkButton>
- <MkButton inline :disabled="logPos == 0" @click="logPos--"><i class="fas fa-angle-left"></i></MkButton>
- <MkButton inline :disabled="logPos == logs.length" @click="logPos++"><i class="fas fa-angle-right"></i></MkButton>
- <MkButton inline :disabled="logPos == logs.length" @click="logPos = logs.length"><i class="fas fa-angle-double-right"></i></MkButton>
- </div>
- <MkButton :disabled="autoplaying" style="margin: var(--margin) auto 0 auto;" @click="autoplay()"><i class="fas fa-play"></i></MkButton>
- </div>
-
- <div class="info">
- <p v-if="game.isLlotheo">{{ $ts._reversi.isLlotheo }}</p>
- <p v-if="game.loopedBoard">{{ $ts._reversi.loopedMap }}</p>
- <p v-if="game.canPutEverywhere">{{ $ts._reversi.canPutEverywhere }}</p>
- </div>
-
- <div class="watchers">
- <MkAvatar v-for="user in watchers" :key="user.id" :user="user" class="avatar"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as CRC32 from 'crc-32';
-import Reversi, { Color } from '@/scripts/games/reversi/core';
-import { url } from '@/config';
-import MkButton from '@/components/ui/button.vue';
-import { userPage } from '@/filters/user';
-import * as os from '@/os';
-import * as sound from '@/scripts/sound';
-
-export default defineComponent({
- components: {
- MkButton
- },
-
- props: {
- initGame: {
- type: Object,
- require: true
- },
- connection: {
- type: Object,
- require: true
- },
- },
-
- data() {
- return {
- game: JSON.parse(JSON.stringify(this.initGame)),
- o: null as Reversi,
- logs: [],
- logPos: 0,
- watchers: [],
- pollingClock: null,
- };
- },
-
- computed: {
- iAmPlayer(): boolean {
- if (!this.$i) return false;
- return this.game.user1Id == this.$i.id || this.game.user2Id == this.$i.id;
- },
-
- myColor(): Color {
- if (!this.iAmPlayer) return null;
- if (this.game.user1Id == this.$i.id && this.game.black == 1) return true;
- if (this.game.user2Id == this.$i.id && this.game.black == 2) return true;
- return false;
- },
-
- opColor(): Color {
- if (!this.iAmPlayer) return null;
- return this.myColor === true ? false : true;
- },
-
- blackUser(): any {
- return this.game.black == 1 ? this.game.user1 : this.game.user2;
- },
-
- whiteUser(): any {
- return this.game.black == 1 ? this.game.user2 : this.game.user1;
- },
-
- cellsStyle(): any {
- return {
- 'grid-template-rows': `repeat(${this.game.map.length}, 1fr)`,
- 'grid-template-columns': `repeat(${this.game.map[0].length}, 1fr)`
- };
- }
- },
-
- watch: {
- logPos(v) {
- if (!this.game.isEnded) return;
- const o = new Reversi(this.game.map, {
- isLlotheo: this.game.isLlotheo,
- canPutEverywhere: this.game.canPutEverywhere,
- loopedBoard: this.game.loopedBoard
- });
- for (const log of this.logs.slice(0, v)) {
- o.put(log.color, log.pos);
- }
- this.o = o;
- //this.$forceUpdate();
- }
- },
-
- created() {
- this.o = new Reversi(this.game.map, {
- isLlotheo: this.game.isLlotheo,
- canPutEverywhere: this.game.canPutEverywhere,
- loopedBoard: this.game.loopedBoard
- });
-
- for (const log of this.game.logs) {
- this.o.put(log.color, log.pos);
- }
-
- this.logs = this.game.logs;
- this.logPos = this.logs.length;
-
- // 通信を取りこぼしてもいいように定期的にポーリングさせる
- if (this.game.isStarted && !this.game.isEnded) {
- this.pollingClock = setInterval(() => {
- if (this.game.isEnded) return;
- const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join(''));
- this.connection.send('check', {
- crc32: crc32
- });
- }, 3000);
- }
- },
-
- mounted() {
- this.connection.on('set', this.onSet);
- this.connection.on('rescue', this.onRescue);
- this.connection.on('ended', this.onEnded);
- this.connection.on('watchers', this.onWatchers);
- },
-
- beforeUnmount() {
- this.connection.off('set', this.onSet);
- this.connection.off('rescue', this.onRescue);
- this.connection.off('ended', this.onEnded);
- this.connection.off('watchers', this.onWatchers);
-
- clearInterval(this.pollingClock);
- },
-
- methods: {
- userPage,
-
- // this.o がリアクティブになった折にはcomputedにできる
- turnUser(): any {
- if (this.o.turn === true) {
- return this.game.black == 1 ? this.game.user1 : this.game.user2;
- } else if (this.o.turn === false) {
- return this.game.black == 1 ? this.game.user2 : this.game.user1;
- } else {
- return null;
- }
- },
-
- // this.o がリアクティブになった折にはcomputedにできる
- isMyTurn(): boolean {
- if (!this.iAmPlayer) return false;
- if (this.turnUser() == null) return false;
- return this.turnUser().id == this.$i.id;
- },
-
- set(pos) {
- if (this.game.isEnded) return;
- if (!this.iAmPlayer) return;
- if (!this.isMyTurn()) return;
- if (!this.o.canPut(this.myColor, pos)) return;
-
- this.o.put(this.myColor, pos);
-
- // サウンドを再生する
- sound.play(this.myColor ? 'reversiPutBlack' : 'reversiPutWhite');
-
- this.connection.send('set', {
- pos: pos
- });
-
- this.checkEnd();
-
- this.$forceUpdate();
- },
-
- onSet(x) {
- this.logs.push(x);
- this.logPos++;
- this.o.put(x.color, x.pos);
- this.checkEnd();
- this.$forceUpdate();
-
- // サウンドを再生する
- if (x.color !== this.myColor) {
- sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite');
- }
- },
-
- onEnded(x) {
- this.game = JSON.parse(JSON.stringify(x.game));
- },
-
- checkEnd() {
- this.game.isEnded = this.o.isEnded;
- if (this.game.isEnded) {
- if (this.o.winner === true) {
- this.game.winnerId = this.game.black == 1 ? this.game.user1Id : this.game.user2Id;
- this.game.winner = this.game.black == 1 ? this.game.user1 : this.game.user2;
- } else if (this.o.winner === false) {
- this.game.winnerId = this.game.black == 1 ? this.game.user2Id : this.game.user1Id;
- this.game.winner = this.game.black == 1 ? this.game.user2 : this.game.user1;
- } else {
- this.game.winnerId = null;
- this.game.winner = null;
- }
- }
- },
-
- // 正しいゲーム情報が送られてきたとき
- onRescue(game) {
- this.game = JSON.parse(JSON.stringify(game));
-
- this.o = new Reversi(this.game.map, {
- isLlotheo: this.game.isLlotheo,
- canPutEverywhere: this.game.canPutEverywhere,
- loopedBoard: this.game.loopedBoard
- });
-
- for (const log of this.game.logs) {
- this.o.put(log.color, log.pos, true);
- }
-
- this.logs = this.game.logs;
- this.logPos = this.logs.length;
-
- this.checkEnd();
- this.$forceUpdate();
- },
-
- onWatchers(users) {
- this.watchers = users;
- },
-
- surrender() {
- os.api('games/reversi/games/surrender', {
- gameId: this.game.id
- });
- },
-
- autoplay() {
- this.autoplaying = true;
- this.logPos = 0;
-
- setTimeout(() => {
- this.logPos = 1;
-
- let i = 1;
- let previousLog = this.game.logs[0];
- const tick = () => {
- const log = this.game.logs[i];
- const time = new Date(log.at).getTime() - new Date(previousLog.at).getTime()
- setTimeout(() => {
- i++;
- this.logPos++;
- previousLog = log;
-
- if (i < this.game.logs.length) {
- tick();
- } else {
- this.autoplaying = false;
- }
- }, time);
- };
-
- tick();
- }, 1000);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-@use "sass:math";
-
-.xqnhankfuuilcwvhgsopeqncafzsquya {
- text-align: center;
-
- > .go-index {
- position: absolute;
- top: 0;
- left: 0;
- z-index: 1;
- width: 42px;
- height :42px;
- }
-
- > header {
- padding: 8px;
- border-bottom: dashed 1px var(--divider);
- }
-
- > .board {
- width: calc(100% - 16px);
- max-width: 500px;
- margin: 0 auto;
-
- $label-size: 16px;
- $gap: 4px;
-
- > .labels-x {
- height: $label-size;
- padding: 0 $label-size;
- display: flex;
-
- > * {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 0.8em;
-
- &:first-child {
- margin-left: -(math.div($gap, 2));
- }
-
- &:last-child {
- margin-right: -(math.div($gap, 2));
- }
- }
- }
-
- > .flex {
- display: flex;
-
- > .labels-y {
- width: $label-size;
- display: flex;
- flex-direction: column;
-
- > * {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
-
- &:first-child {
- margin-top: -(math.div($gap, 2));
- }
-
- &:last-child {
- margin-bottom: -(math.div($gap, 2));
- }
- }
- }
-
- > .cells {
- flex: 1;
- display: grid;
- grid-gap: $gap;
-
- > div {
- background: transparent;
- border-radius: 6px;
- overflow: hidden;
-
- * {
- pointer-events: none;
- user-select: none;
- }
-
- &.empty {
- border: solid 2px var(--divider);
- }
-
- &.empty.can {
- border-color: var(--accent);
- }
-
- &.empty.myTurn {
- border-color: var(--divider);
-
- &.can {
- border-color: var(--accent);
- cursor: pointer;
-
- &:hover {
- background: var(--accent);
- }
- }
- }
-
- &.prev {
- box-shadow: 0 0 0 4px var(--accent);
- }
-
- &.isEnded {
- border-color: var(--divider);
- }
-
- &.none {
- border-color: transparent !important;
- }
-
- > svg, > img {
- display: block;
- width: 100%;
- height: 100%;
- }
- }
- }
- }
- }
-
- > .status {
- margin: 0;
- padding: 16px 0;
- }
-
- > .actions {
- padding-bottom: 16px;
- }
-
- > .player {
- padding: 0 16px 32px 16px;
- margin: 0 auto;
- max-width: 500px;
-
- > span {
- display: inline-block;
- margin: 0 8px;
- min-width: 70px;
- }
-
- > .buttons {
- display: flex;
-
- > * {
- flex: 1;
- }
- }
- }
-
- > .watchers {
- padding: 0 0 16px 0;
-
- &:empty {
- display: none;
- }
-
- > .avatar {
- width: 32px;
- height: 32px;
- }
- }
-}
-</style>
diff --git a/packages/client/src/pages/reversi/game.setting.vue b/packages/client/src/pages/reversi/game.setting.vue
deleted file mode 100644
index 28bc598cfd..0000000000
--- a/packages/client/src/pages/reversi/game.setting.vue
+++ /dev/null
@@ -1,390 +0,0 @@
-<template>
-<div class="urbixznjwwuukfsckrwzwsqzsxornqij">
- <header><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></header>
-
- <div>
- <p>{{ $ts._reversi.gameSettings }}</p>
-
- <div class="card map _panel">
- <header>
- <select v-model="mapName" :placeholder="$ts._reversi.chooseBoard" @change="onMapChange">
- <option v-if="mapName == '-Custom-'" label="-Custom-" :value="mapName"/>
- <option :label="$ts.random" :value="null"/>
- <optgroup v-for="c in mapCategories" :key="c" :label="c">
- <option v-for="m in Object.values(maps).filter(m => m.category == c)" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option>
- </optgroup>
- </select>
- </header>
-
- <div>
- <div v-if="game.map == null" class="random"><i class="fas fa-dice"></i></div>
- <div v-else class="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="{ none: x == ' ' }" @click="onPixelClick(i, x)">
- <i v-if="x === 'b'" class="fas fa-circle"></i>
- <i v-if="x === 'w'" class="far fa-circle"></i>
- </div>
- </div>
- </div>
- </div>
-
- <div class="card _panel">
- <header>
- <span>{{ $ts._reversi.blackOrWhite }}</span>
- </header>
-
- <div>
- <MkRadio v-model="game.bw" value="random" @update:modelValue="updateSettings('bw')">{{ $ts.random }}</MkRadio>
- <MkRadio v-model="game.bw" :value="'1'" @update:modelValue="updateSettings('bw')">
- <I18n :src="$ts._reversi.blackIs" tag="span">
- <template #name>
- <b><MkUserName :user="game.user1"/></b>
- </template>
- </I18n>
- </MkRadio>
- <MkRadio v-model="game.bw" :value="'2'" @update:modelValue="updateSettings('bw')">
- <I18n :src="$ts._reversi.blackIs" tag="span">
- <template #name>
- <b><MkUserName :user="game.user2"/></b>
- </template>
- </I18n>
- </MkRadio>
- </div>
- </div>
-
- <div class="card _panel">
- <header>
- <span>{{ $ts._reversi.rules }}</span>
- </header>
-
- <div>
- <MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ $ts._reversi.isLlotheo }}</MkSwitch>
- <MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ $ts._reversi.loopedMap }}</MkSwitch>
- <MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ $ts._reversi.canPutEverywhere }}</MkSwitch>
- </div>
- </div>
-
- <div v-if="form" class="card form _panel">
- <header>
- <span>{{ $ts._reversi.botSettings }}</span>
- </header>
-
- <div>
- <template v-for="item in form">
- <MkSwitch v-if="item.type == 'switch'" :key="item.id" v-model="item.value" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</MkSwitch>
-
- <div v-if="item.type == 'radio'" :key="item.id" class="card">
- <header>
- <span>{{ item.label }}</span>
- </header>
-
- <div>
- <MkRadio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :value="r.value" @update:modelValue="onChangeForm(item)">{{ r.label }}</MkRadio>
- </div>
- </div>
-
- <div v-if="item.type == 'slider'" :key="item.id" class="card">
- <header>
- <span>{{ item.label }}</span>
- </header>
-
- <div>
- <input v-model="item.value" type="range" :min="item.min" :max="item.max" :step="item.step || 1" @change="onChangeForm(item)"/>
- </div>
- </div>
-
- <div v-if="item.type == 'textbox'" :key="item.id" class="card">
- <header>
- <span>{{ item.label }}</span>
- </header>
-
- <div>
- <input v-model="item.value" @change="onChangeForm(item)"/>
- </div>
- </div>
- </template>
- </div>
- </div>
- </div>
-
- <footer class="_acrylic">
- <p class="status">
- <template v-if="isAccepted && isOpAccepted">{{ $ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template>
- <template v-if="isAccepted && !isOpAccepted">{{ $ts._reversi.waitingForOther }}<MkEllipsis/></template>
- <template v-if="!isAccepted && isOpAccepted">{{ $ts._reversi.waitingForMe }}</template>
- <template v-if="!isAccepted && !isOpAccepted">{{ $ts._reversi.waitingBoth }}<MkEllipsis/></template>
- </p>
-
- <div class="actions">
- <MkButton inline @click="exit">{{ $ts.cancel }}</MkButton>
- <MkButton v-if="!isAccepted" inline primary @click="accept">{{ $ts._reversi.ready }}</MkButton>
- <MkButton v-if="isAccepted" inline primary @click="cancel">{{ $ts._reversi.cancelReady }}</MkButton>
- </div>
- </footer>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as maps from '@/scripts/games/reversi/maps';
-import MkButton from '@/components/ui/button.vue';
-import MkSwitch from '@/components/form/switch.vue';
-import MkRadio from '@/components/form/radio.vue';
-
-export default defineComponent({
- components: {
- MkButton,
- MkSwitch,
- MkRadio,
- },
-
- props: {
- initGame: {
- type: Object,
- require: true
- },
- connection: {
- type: Object,
- require: true
- },
- },
-
- data() {
- return {
- game: this.initGame,
- o: null,
- isLlotheo: false,
- mapName: maps.eighteight.name,
- maps: maps,
- form: null,
- messages: [],
- };
- },
-
- computed: {
- mapCategories(): string[] {
- const categories = Object.values(maps).map(x => x.category);
- return categories.filter((item, pos) => categories.indexOf(item) == pos);
- },
- isAccepted(): boolean {
- if (this.game.user1Id == this.$i.id && this.game.user1Accepted) return true;
- if (this.game.user2Id == this.$i.id && this.game.user2Accepted) return true;
- return false;
- },
- isOpAccepted(): boolean {
- if (this.game.user1Id != this.$i.id && this.game.user1Accepted) return true;
- if (this.game.user2Id != this.$i.id && this.game.user2Accepted) return true;
- return false;
- }
- },
-
- created() {
- this.connection.on('changeAccepts', this.onChangeAccepts);
- this.connection.on('updateSettings', this.onUpdateSettings);
- this.connection.on('initForm', this.onInitForm);
- this.connection.on('message', this.onMessage);
-
- if (this.game.user1Id != this.$i.id && this.game.form1) this.form = this.game.form1;
- if (this.game.user2Id != this.$i.id && this.game.form2) this.form = this.game.form2;
- },
-
- beforeUnmount() {
- this.connection.off('changeAccepts', this.onChangeAccepts);
- this.connection.off('updateSettings', this.onUpdateSettings);
- this.connection.off('initForm', this.onInitForm);
- this.connection.off('message', this.onMessage);
- },
-
- methods: {
- exit() {
-
- },
-
- accept() {
- this.connection.send('accept', {});
- },
-
- cancel() {
- this.connection.send('cancelAccept', {});
- },
-
- onChangeAccepts(accepts) {
- this.game.user1Accepted = accepts.user1;
- this.game.user2Accepted = accepts.user2;
- },
-
- updateSettings(key: string) {
- this.connection.send('updateSettings', {
- key: key,
- value: this.game[key]
- });
- },
-
- onUpdateSettings({ key, value }) {
- this.game[key] = value;
- if (this.game.map == null) {
- this.mapName = null;
- } else {
- const found = Object.values(maps).find(x => x.data.join('') == this.game.map.join(''));
- this.mapName = found ? found.name : '-Custom-';
- }
- },
-
- onInitForm(x) {
- if (x.userId == this.$i.id) return;
- this.form = x.form;
- },
-
- onMessage(x) {
- if (x.userId == this.$i.id) return;
- this.messages.unshift(x.message);
- },
-
- onChangeForm(item) {
- this.connection.send('updateForm', {
- id: item.id,
- value: item.value
- });
- },
-
- onMapChange() {
- if (this.mapName == null) {
- this.game.map = null;
- } else {
- this.game.map = Object.values(maps).find(x => x.name == this.mapName).data;
- }
- this.updateSettings('map');
- },
-
- onPixelClick(pos, pixel) {
- const x = pos % this.game.map[0].length;
- const y = Math.floor(pos / this.game.map[0].length);
- const newPixel =
- pixel == ' ' ? '-' :
- pixel == '-' ? 'b' :
- pixel == 'b' ? 'w' :
- ' ';
- const line = this.game.map[y].split('');
- line[x] = newPixel;
- this.game.map[y] = line.join('');
- this.updateSettings('map');
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.urbixznjwwuukfsckrwzwsqzsxornqij {
- text-align: center;
- background: var(--bg);
-
- > header {
- padding: 8px;
- border-bottom: dashed 1px #c4cdd4;
- }
-
- > div {
- padding: 0 16px;
-
- > .card {
- margin: 0 auto 16px auto;
-
- &.map {
- > header {
- > select {
- width: 100%;
- padding: 12px 14px;
- background: var(--face);
- border: 1px solid var(--inputBorder);
- border-radius: 4px;
- color: var(--fg);
- cursor: pointer;
- transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
-
- &:focus-visible,
- &:active {
- border-color: var(--accent);
- }
- }
- }
-
- > div {
- > .random {
- padding: 32px 0;
- font-size: 64px;
- color: var(--fg);
- opacity: 0.7;
- }
-
- > .board {
- display: grid;
- grid-gap: 4px;
- width: 300px;
- height: 300px;
- margin: 0 auto;
- color: var(--fg);
-
- > div {
- background: transparent;
- border: solid 2px var(--divider);
- border-radius: 6px;
- overflow: hidden;
- cursor: pointer;
-
- * {
- pointer-events: none;
- user-select: none;
- width: 100%;
- height: 100%;
- }
-
- &.none {
- border-color: transparent;
- }
- }
- }
- }
- }
-
- &.form {
- > div {
- > .card + .card {
- margin-top: 16px;
- }
-
- input[type='range'] {
- width: 100%;
- }
- }
- }
- }
-
- .card {
- max-width: 400px;
-
- > header {
- padding: 18px 20px;
- border-bottom: 1px solid var(--divider);
- }
-
- > div {
- padding: 20px;
- color: var(--fg);
- }
- }
- }
-
- > footer {
- position: sticky;
- bottom: 0;
- padding: 16px;
- border-top: solid 1px var(--divider);
-
- > .status {
- margin: 0 0 16px 0;
- }
- }
-}
-</style>
diff --git a/packages/client/src/pages/reversi/game.vue b/packages/client/src/pages/reversi/game.vue
deleted file mode 100644
index b1ed632904..0000000000
--- a/packages/client/src/pages/reversi/game.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
-<div v-if="game == null"><MkLoading/></div>
-<GameSetting v-else-if="!game.isStarted" :init-game="game" :connection="connection"/>
-<GameBoard v-else :init-game="game" :connection="connection"/>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import GameSetting from './game.setting.vue';
-import GameBoard from './game.board.vue';
-import * as os from '@/os';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- GameSetting,
- GameBoard,
- },
-
- props: {
- gameId: {
- type: String,
- required: true
- },
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts._reversi.reversi,
- icon: 'fas fa-gamepad'
- },
- game: null,
- connection: null,
- };
- },
-
- watch: {
- gameId() {
- this.fetch();
- }
- },
-
- mounted() {
- this.fetch();
- },
-
- beforeUnmount() {
- if (this.connection) {
- this.connection.dispose();
- }
- },
-
- methods: {
- fetch() {
- os.api('games/reversi/games/show', {
- gameId: this.gameId
- }).then(game => {
- this.game = game;
-
- if (this.connection) {
- this.connection.dispose();
- }
- this.connection = markRaw(os.stream.useChannel('gamesReversiGame', {
- gameId: this.game.id
- }));
- this.connection.on('started', this.onStarted);
- });
- },
-
- onStarted(game) {
- Object.assign(this.game, game);
- },
- }
-});
-</script>
diff --git a/packages/client/src/pages/reversi/index.vue b/packages/client/src/pages/reversi/index.vue
deleted file mode 100644
index 0b118531fc..0000000000
--- a/packages/client/src/pages/reversi/index.vue
+++ /dev/null
@@ -1,279 +0,0 @@
-<template>
-<div v-if="!matching" class="bgvwxkhb">
- <h1>Misskey {{ $ts._reversi.reversi }}</h1>
-
- <div class="play">
- <MkButton primary round style="margin: var(--margin) auto 0 auto;" @click="match">{{ $ts.invite }}</MkButton>
- </div>
-
- <div class="_section">
- <MkFolder v-if="invitations.length > 0">
- <template #header>{{ $ts.invitations }}</template>
- <div class="nfcacttm">
- <button v-for="invitation in invitations" class="invitation _panel _button" tabindex="-1" @click="accept(invitation)">
- <MkAvatar class="avatar" :user="invitation.parent" :show-indicator="true"/>
- <span class="name"><b><MkUserName :user="invitation.parent"/></b></span>
- <span class="username">@{{ invitation.parent.username }}</span>
- <MkTime :time="invitation.createdAt" class="time"/>
- </button>
- </div>
- </MkFolder>
-
- <MkFolder v-if="myGames.length > 0">
- <template #header>{{ $ts._reversi.myGames }}</template>
- <div class="knextgwz">
- <MkA v-for="g in myGames" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`">
- <div class="players">
- <MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/>
- </div>
- <footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer>
- </MkA>
- </div>
- </MkFolder>
-
- <MkFolder v-if="games.length > 0">
- <template #header>{{ $ts._reversi.allGames }}</template>
- <div class="knextgwz">
- <MkA v-for="g in games" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`">
- <div class="players">
- <MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/>
- </div>
- <footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer>
- </MkA>
- </div>
- </MkFolder>
- </div>
-</div>
-<div v-else class="sazhgisb">
- <h1>
- <I18n :src="$ts.waitingFor" tag="span">
- <template #x>
- <b><MkUserName :user="matching"/></b>
- </template>
- </I18n>
- <MkEllipsis/>
- </h1>
- <div class="cancel">
- <MkButton inline round @click="cancel">{{ $ts.cancel }}</MkButton>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import * as os from '@/os';
-import MkButton from '@/components/ui/button.vue';
-import MkFolder from '@/components/ui/folder.vue';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- MkButton, MkFolder,
- },
-
- inject: ['navHook'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts._reversi.reversi,
- icon: 'fas fa-gamepad'
- },
- games: [],
- gamesFetching: true,
- gamesMoreFetching: false,
- myGames: [],
- matching: null,
- invitations: [],
- connection: null,
- pingClock: null,
- };
- },
-
- mounted() {
- if (this.$i) {
- this.connection = markRaw(os.stream.useChannel('gamesReversi'));
-
- this.connection.on('invited', this.onInvited);
-
- this.connection.on('matched', this.onMatched);
-
- this.pingClock = setInterval(() => {
- if (this.matching) {
- this.connection.send('ping', {
- id: this.matching.id
- });
- }
- }, 3000);
-
- os.api('games/reversi/games', {
- my: true
- }).then(games => {
- this.myGames = games;
- });
-
- os.api('games/reversi/invitations').then(invitations => {
- this.invitations = this.invitations.concat(invitations);
- });
- }
-
- os.api('games/reversi/games').then(games => {
- this.games = games;
- this.gamesFetching = false;
- });
- },
-
- beforeUnmount() {
- if (this.connection) {
- this.connection.dispose();
- clearInterval(this.pingClock);
- }
- },
-
- methods: {
- go(game) {
- const url = '/games/reversi/' + game.id;
- if (this.navHook) {
- this.navHook(url);
- } else {
- this.$router.push(url);
- }
- },
-
- async match() {
- const user = await os.selectUser({ local: true });
- if (user == null) return;
- os.api('games/reversi/match', {
- userId: user.id
- }).then(res => {
- if (res == null) {
- this.matching = user;
- } else {
- this.go(res);
- }
- });
- },
-
- cancel() {
- this.matching = null;
- os.api('games/reversi/match/cancel');
- },
-
- accept(invitation) {
- os.api('games/reversi/match', {
- userId: invitation.parent.id
- }).then(game => {
- if (game) {
- this.go(game);
- }
- });
- },
-
- onMatched(game) {
- this.go(game);
- },
-
- onInvited(invite) {
- this.invitations.unshift(invite);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.bgvwxkhb {
- > h1 {
- margin: 0;
- padding: 24px;
- text-align: center;
- font-size: 1.5em;
- background: linear-gradient(0deg, #43c583, #438881);
- color: #fff;
- }
-
- > .play {
- text-align: center;
- }
-}
-
-.sazhgisb {
- text-align: center;
-}
-
-.nfcacttm {
- > .invitation {
- display: flex;
- box-sizing: border-box;
- width: 100%;
- padding: 16px;
- line-height: 32px;
- text-align: left;
-
- > .avatar {
- width: 32px;
- height: 32px;
- margin-right: 8px;
- }
-
- > .name {
- margin-right: 8px;
- }
-
- > .username {
- margin-right: 8px;
- opacity: 0.7;
- }
-
- > .time {
- margin-left: auto;
- opacity: 0.7;
- }
- }
-}
-
-.knextgwz {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
- grid-gap: var(--margin);
-
- > .game {
- > .players {
- text-align: center;
- padding: 16px;
- line-height: 32px;
-
- > .avatar {
- width: 32px;
- height: 32px;
-
- &:first-child {
- margin-right: 8px;
- }
-
- &:last-child {
- margin-left: 8px;
- }
- }
- }
-
- > footer {
- display: flex;
- align-items: baseline;
- border-top: solid 0.5px var(--divider);
- padding: 6px 8px;
- font-size: 0.9em;
-
- > .state {
- &.playing {
- color: var(--accent);
- }
- }
-
- > .time {
- margin-left: auto;
- opacity: 0.7;
- }
- }
- }
-}
-</style>
diff --git a/packages/client/src/pages/room/preview.vue b/packages/client/src/pages/room/preview.vue
deleted file mode 100644
index b0e600d4fb..0000000000
--- a/packages/client/src/pages/room/preview.vue
+++ /dev/null
@@ -1,107 +0,0 @@
-<template>
-<canvas width="224" height="128"></canvas>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as THREE from 'three';
-import * as os from '@/os';
-
-export default defineComponent({
- data() {
- return {
- selected: null,
- objectHeight: 0,
- orbitRadius: 5
- };
- },
-
- mounted() {
- const canvas = this.$el;
-
- const width = canvas.width;
- const height = canvas.height;
-
- const scene = new THREE.Scene();
-
- const renderer = new THREE.WebGLRenderer({
- canvas: canvas,
- antialias: true,
- alpha: false
- });
- renderer.setPixelRatio(window.devicePixelRatio);
- renderer.setSize(width, height);
- renderer.setClearColor(0x000000);
- renderer.autoClear = false;
- renderer.shadowMap.enabled = true;
- renderer.shadowMap.cullFace = THREE.CullFaceBack;
-
- const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
- camera.zoom = 10;
- camera.position.x = 0;
- camera.position.y = 2;
- camera.position.z = 0;
- camera.updateProjectionMatrix();
- scene.add(camera);
-
- const ambientLight = new THREE.AmbientLight(0xffffff, 1);
- ambientLight.castShadow = false;
- scene.add(ambientLight);
-
- const light = new THREE.PointLight(0xffffff, 1, 100);
- light.position.set(3, 3, 3);
- scene.add(light);
-
- const grid = new THREE.GridHelper(5, 16, 0x444444, 0x222222);
- scene.add(grid);
-
- const render = () => {
- const timer = Date.now() * 0.0004;
- requestAnimationFrame(render);
-
- camera.position.y = Math.sin(Math.PI / 6) * this.orbitRadius; // Math.PI / 6 => 30deg
- camera.position.z = Math.cos(timer) * this.orbitRadius;
- camera.position.x = Math.sin(timer) * this.orbitRadius;
- camera.lookAt(new THREE.Vector3(0, this.objectHeight / 2, 0));
- renderer.render(scene, camera);
- };
-
- this.selected = selected => {
- const obj = selected.clone();
-
- // Remove current object
- const current = scene.getObjectByName('obj');
- if (current != null) {
- scene.remove(current);
- }
-
- // Add new object
- obj.name = 'obj';
- obj.position.x = 0;
- obj.position.y = 0;
- obj.position.z = 0;
- obj.rotation.x = 0;
- obj.rotation.y = 0;
- obj.rotation.z = 0;
- obj.traverse(child => {
- if (child instanceof THREE.Mesh) {
- child.material = child.material.clone();
- return child.material.emissive.setHex(0x000000);
- }
- });
- const objectBoundingBox = new THREE.Box3().setFromObject(obj);
- this.objectHeight = objectBoundingBox.max.y - objectBoundingBox.min.y;
-
- const objectWidth = objectBoundingBox.max.x - objectBoundingBox.min.x;
- const objectDepth = objectBoundingBox.max.z - objectBoundingBox.min.z;
-
- const horizontal = Math.hypot(objectWidth, objectDepth) / camera.aspect;
- this.orbitRadius = Math.max(horizontal, this.objectHeight) * camera.zoom * 0.625 / Math.tan(camera.fov * 0.5 * (Math.PI / 180));
-
- scene.add(obj);
- };
-
- render();
- },
-});
-</script>
diff --git a/packages/client/src/pages/room/room.vue b/packages/client/src/pages/room/room.vue
deleted file mode 100644
index eb85d39dc4..0000000000
--- a/packages/client/src/pages/room/room.vue
+++ /dev/null
@@ -1,279 +0,0 @@
-<template>
-<div class="hveuntkp">
- <div v-if="objectSelected" class="controller _section">
- <div class="_content">
- <p class="name">{{ selectedFurnitureName }}</p>
- <XPreview ref="preview"/>
- <template v-if="selectedFurnitureInfo.props">
- <div v-for="k in Object.keys(selectedFurnitureInfo.props)" :key="k">
- <p>{{ k }}</p>
- <template v-if="selectedFurnitureInfo.props[k] === 'image'">
- <MkButton @click="chooseImage(k, $event)">{{ $ts._rooms.chooseImage }}</MkButton>
- </template>
- <template v-else-if="selectedFurnitureInfo.props[k] === 'color'">
- <input type="color" :value="selectedFurnitureProps ? selectedFurnitureProps[k] : null" @change="updateColor(k, $event)"/>
- </template>
- </div>
- </template>
- </div>
- <div class="_content">
- <MkButton inline :primary="isTranslateMode" @click="translate()"><i class="fas fa-arrows-alt"></i> {{ $ts._rooms.translate }}</MkButton>
- <MkButton inline :primary="isRotateMode" @click="rotate()"><i class="fas fa-undo"></i> {{ $ts._rooms.rotate }}</MkButton>
- <MkButton v-if="isTranslateMode || isRotateMode" inline @click="exit()"><i class="fas fa-ban"></i> {{ $ts._rooms.exit }}</MkButton>
- </div>
- <div class="_content">
- <MkButton @click="remove()"><i class="fas fa-trash-alt"></i> {{ $ts._rooms.remove }}</MkButton>
- </div>
- </div>
-
- <div v-if="isMyRoom" class="menu _section">
- <div class="_content">
- <MkButton @click="add()"><i class="fas fa-box-open"></i> {{ $ts._rooms.addFurniture }}</MkButton>
- </div>
- <div class="_content">
- <MkSelect :model-value="roomType" @update:modelValue="updateRoomType($event)">
- <template #label>{{ $ts._rooms.roomType }}</template>
- <option value="default">{{ $ts._rooms._roomType.default }}</option>
- <option value="washitsu">{{ $ts._rooms._roomType.washitsu }}</option>
- </MkSelect>
- <label v-if="roomType === 'default'">
- <span>{{ $ts._rooms.carpetColor }}</span>
- <input type="color" :value="carpetColor" @change="updateCarpetColor($event)"/>
- </label>
- </div>
- <div class="_content">
- <MkButton inline :disabled="!changed" primary @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
- <MkButton inline @click="clear()"><i class="fas fa-broom"></i> {{ $ts._rooms.clear }}</MkButton>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import { Room } from '@/scripts/room/room';
-import * as Acct from 'misskey-js/built/acct';
-import XPreview from './preview.vue';
-const storeItems = require('@/scripts/room/furnitures.json5');
-import { query as urlQuery } from '@/scripts/url';
-import MkButton from '@/components/ui/button.vue';
-import MkSelect from '@/components/form/select.vue';
-import { selectFile } from '@/scripts/select-file';
-import * as os from '@/os';
-import { ColdDeviceStorage } from '@/store';
-import * as symbols from '@/symbols';
-
-let room: Room;
-
-export default defineComponent({
- components: {
- XPreview,
- MkButton,
- MkSelect,
- },
-
- beforeRouteLeave(to, from, next) {
- if (this.changed) {
- os.confirm({
- type: 'warning',
- text: this.$ts.leaveConfirm,
- }).then(({ canceled }) => {
- if (canceled) {
- next(false);
- } else {
- next();
- }
- });
- } else {
- next();
- }
- },
-
- props: {
- acct: {
- type: String,
- required: true
- },
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.user ? {
- title: this.$ts.room,
- avatar: this.user,
- } : null),
- user: null,
- objectSelected: false,
- selectedFurnitureName: null,
- selectedFurnitureInfo: null,
- selectedFurnitureProps: null,
- roomType: null,
- carpetColor: null,
- isTranslateMode: false,
- isRotateMode: false,
- isMyRoom: false,
- changed: false,
- };
- },
-
- async mounted() {
- window.addEventListener('beforeunload', this.beforeunload);
-
- this.user = await os.api('users/show', {
- ...Acct.parse(this.acct)
- });
-
- this.isMyRoom = this.$i && (this.$i.id === this.user.id);
-
- const roomInfo = await os.api('room/show', {
- userId: this.user.id
- });
-
- this.roomType = roomInfo.roomType;
- this.carpetColor = roomInfo.carpetColor;
-
- room = new Room(this.user, this.isMyRoom, roomInfo, this.$el, {
- graphicsQuality: ColdDeviceStorage.get('roomGraphicsQuality'),
- onChangeSelect: obj => {
- this.objectSelected = obj != null;
- if (obj) {
- const f = room.findFurnitureById(obj.name);
- this.selectedFurnitureName = this.$t('_rooms._furnitures.' + f.type);
- this.selectedFurnitureInfo = storeItems.find(x => x.id === f.type);
- this.selectedFurnitureProps = f.props
- ? JSON.parse(JSON.stringify(f.props)) // Disable reactivity
- : null;
- this.$nextTick(() => {
- this.$refs.preview.selected(obj);
- });
- }
- },
- useOrthographicCamera: ColdDeviceStorage.get('roomUseOrthographicCamera'),
- });
- },
-
- beforeUnmount() {
- room.destroy();
- window.removeEventListener('beforeunload', this.beforeunload);
- },
-
- methods: {
- beforeunload(e: BeforeUnloadEvent) {
- if (this.changed) {
- e.preventDefault();
- e.returnValue = '';
- }
- },
-
- async add() {
- const { canceled, result: id } = await os.select({
- title: this.$ts._rooms.addFurniture,
- items: storeItems.map(item => ({
- value: item.id, text: this.$t('_rooms._furnitures.' + item.id)
- }))
- });
- if (canceled) return;
- room.addFurniture(id);
- this.changed = true;
- },
-
- remove() {
- this.isTranslateMode = false;
- this.isRotateMode = false;
- room.removeFurniture();
- this.changed = true;
- },
-
- save() {
- os.api('room/update', {
- room: room.getRoomInfo()
- }).then(() => {
- this.changed = false;
- os.success();
- }).catch((e: any) => {
- os.alert({
- type: 'error',
- text: e.message
- });
- });
- },
-
- clear() {
- os.confirm({
- type: 'warning',
- text: this.$ts._rooms.clearConfirm,
- }).then(({ canceled }) => {
- if (canceled) return;
- room.removeAllFurnitures();
- this.changed = true;
- });
- },
-
- chooseImage(key, e) {
- selectFile(e.currentTarget || e.target, null).then(file => {
- room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`);
- this.$refs.preview.selected(room.getSelectedObject());
- this.changed = true;
- });
- },
-
- updateColor(key, ev) {
- room.updateProp(key, ev.target.value);
- this.$refs.preview.selected(room.getSelectedObject());
- this.changed = true;
- },
-
- updateCarpetColor(ev) {
- room.updateCarpetColor(ev.target.value);
- this.carpetColor = ev.target.value;
- this.changed = true;
- },
-
- updateRoomType(type) {
- room.changeRoomType(type);
- this.roomType = type;
- this.changed = true;
- },
-
- translate() {
- if (this.isTranslateMode) {
- this.exit();
- } else {
- this.isRotateMode = false;
- this.isTranslateMode = true;
- room.enterTransformMode('translate');
- }
- this.changed = true;
- },
-
- rotate() {
- if (this.isRotateMode) {
- this.exit();
- } else {
- this.isTranslateMode = false;
- this.isRotateMode = true;
- room.enterTransformMode('rotate');
- }
- this.changed = true;
- },
-
- exit() {
- this.isTranslateMode = false;
- this.isRotateMode = false;
- room.exitTransformMode();
- this.changed = true;
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.hveuntkp {
- position: relative;
- min-height: 500px;
-
- > ::v-deep(canvas) {
- display: block;
- }
-}
-</style>
diff --git a/packages/client/src/pages/search.vue b/packages/client/src/pages/search.vue
index 85d19bb255..ce2b7035da 100644
--- a/packages/client/src/pages/search.vue
+++ b/packages/client/src/pages/search.vue
@@ -6,37 +6,31 @@
</div>
</template>
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- XNotes
- },
+const props = defineProps<{
+ query: string;
+ channel?: string;
+}>();
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: computed(() => this.$t('searchWith', { q: this.$route.query.q })),
- icon: 'fas fa-search',
- },
- pagination: {
- endpoint: 'notes/search',
- limit: 10,
- params: () => ({
- query: this.$route.query.q,
- channelId: this.$route.query.channel,
- })
- },
- };
- },
+const pagination = {
+ endpoint: 'notes/search' as const,
+ limit: 10,
+ params: computed(() => ({
+ query: props.query,
+ channelId: props.channel,
+ }))
+};
- watch: {
- $route() {
- (this.$refs.notes as any).reload();
- }
- },
+defineExpose({
+ [symbols.PAGE_INFO]: computed(() => ({
+ title: i18n.t('searchWith', { q: props.query }),
+ icon: 'fas fa-search',
+ bg: 'var(--bg)',
+ })),
});
</script>
diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index cffd10a0ee..10599d99ff 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -71,9 +71,6 @@ import MkButton from '@/components/ui/button.vue';
import MkInfo from '@/components/ui/info.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue
index f3d5e2f2c3..c98ad056f6 100644
--- a/packages/client/src/pages/settings/account-info.vue
+++ b/packages/client/src/pages/settings/account-info.vue
@@ -1,144 +1,135 @@
<template>
-<FormBase>
- <FormKeyValueView>
+<div class="_formRoot">
+ <MkKeyValue>
<template #key>ID</template>
<template #value><span class="_monospace">{{ $i.id }}</span></template>
- </FormKeyValueView>
+ </MkKeyValue>
- <FormGroup>
- <FormKeyValueView>
+ <FormSection>
+ <MkKeyValue>
<template #key>{{ $ts.registeredDate }}</template>
<template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
- </FormKeyValueView>
- </FormGroup>
+ </MkKeyValue>
+ </FormSection>
- <FormGroup v-if="stats">
+ <FormSection v-if="stats">
<template #label>{{ $ts.statistics }}</template>
- <FormKeyValueView>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.notesCount }}</template>
<template #value>{{ number(stats.notesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.repliesCount }}</template>
<template #value>{{ number(stats.repliesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.renotesCount }}</template>
<template #value>{{ number(stats.renotesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.repliedCount }}</template>
<template #value>{{ number(stats.repliedCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.renotedCount }}</template>
<template #value>{{ number(stats.renotedCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.pollVotesCount }}</template>
<template #value>{{ number(stats.pollVotesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.pollVotedCount }}</template>
<template #value>{{ number(stats.pollVotedCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.sentReactionsCount }}</template>
<template #value>{{ number(stats.sentReactionsCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.receivedReactionsCount }}</template>
<template #value>{{ number(stats.receivedReactionsCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.noteFavoritesCount }}</template>
<template #value>{{ number(stats.noteFavoritesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followingCount }}</template>
<template #value>{{ number(stats.followingCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template>
<template #value>{{ number(stats.localFollowingCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template>
<template #value>{{ number(stats.remoteFollowingCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followersCount }}</template>
<template #value>{{ number(stats.followersCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template>
<template #value>{{ number(stats.localFollowersCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template>
<template #value>{{ number(stats.remoteFollowersCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.pageLikesCount }}</template>
<template #value>{{ number(stats.pageLikesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.pageLikedCount }}</template>
<template #value>{{ number(stats.pageLikedCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.driveFilesCount }}</template>
<template #value>{{ number(stats.driveFilesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.driveUsage }}</template>
<template #value>{{ bytes(stats.driveUsage) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.reversiCount }}</template>
- <template #value>{{ number(stats.reversiCount) }}</template>
- </FormKeyValueView>
- </FormGroup>
+ </MkKeyValue>
+ </FormSection>
- <FormGroup>
+ <FormSection>
<template #label>{{ $ts.other }}</template>
- <FormKeyValueView>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>emailVerified</template>
<template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>twoFactorEnabled</template>
<template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>securityKeys</template>
<template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>usePasswordLessLogin</template>
<template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>isModerator</template>
<template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
<template #key>isAdmin</template>
<template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- </FormGroup>
-</FormBase>
+ </MkKeyValue>
+ </FormSection>
+</div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
-import FormSwitch from '@/components/form/switch.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
+import FormSection from '@/components/form/section.vue';
+import MkKeyValue from '@/components/key-value.vue';
import * as os from '@/os';
import number from '@/filters/number';
import bytes from '@/filters/bytes';
@@ -146,13 +137,8 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
- FormSelect,
- FormSwitch,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
+ FormSection,
+ MkKeyValue,
},
emits: ['info'],
@@ -168,8 +154,6 @@ export default defineComponent({
},
mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
os.api('users/stats', {
userId: this.$i.id
}).then(stats => {
diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue
index 2d1e0eff4e..c795ede8ac 100644
--- a/packages/client/src/pages/settings/accounts.vue
+++ b/packages/client/src/pages/settings/accounts.vue
@@ -1,41 +1,35 @@
<template>
-<FormBase>
+<div class="_formRoot">
<FormSuspense :p="init">
<FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton>
- <div v-for="account in accounts" :key="account.id" class="_debobigegoItem _button" @click="menu(account, $event)">
- <div class="_debobigegoPanel lcjjdxlm">
- <div class="avatar">
- <MkAvatar :user="account" class="avatar"/>
+ <div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)">
+ <div class="avatar">
+ <MkAvatar :user="account" class="avatar"/>
+ </div>
+ <div class="body">
+ <div class="name">
+ <MkUserName :user="account"/>
</div>
- <div class="body">
- <div class="name">
- <MkUserName :user="account"/>
- </div>
- <div class="acct">
- <MkAcct :user="account"/>
- </div>
+ <div class="acct">
+ <MkAcct :user="account"/>
</div>
</div>
</div>
</FormSuspense>
-</FormBase>
+</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
+import FormSuspense from '@/components/form/suspense.vue';
+import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { getAccounts, addAccount, login } from '@/account';
export default defineComponent({
components: {
- FormBase,
FormSuspense,
FormButton,
},
@@ -59,10 +53,6 @@ export default defineComponent({
};
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
menu(account, ev) {
os.popupMenu([{
diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue
index 30a4902a15..20ff2a8d96 100644
--- a/packages/client/src/pages/settings/api.vue
+++ b/packages/client/src/pages/settings/api.vue
@@ -1,25 +1,20 @@
<template>
-<FormBase>
- <FormButton primary @click="generateToken">{{ $ts.generateAccessToken }}</FormButton>
- <FormLink to="/settings/apps">{{ $ts.manageAccessTokens }}</FormLink>
- <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink>
-</FormBase>
+<div class="_formRoot">
+ <FormButton primary class="_formBlock" @click="generateToken">{{ $ts.generateAccessToken }}</FormButton>
+ <FormLink to="/settings/apps" class="_formBlock">{{ $ts.manageAccessTokens }}</FormLink>
+ <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null" class="_formBlock">API console</FormLink>
+</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/form/switch.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
+import FormLink from '@/components/form/link.vue';
+import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
FormButton,
FormLink,
},
@@ -37,10 +32,6 @@ export default defineComponent({
};
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
generateToken() {
os.popup(import('@/components/token-generate-window.vue'), {}, {
diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue
index b5fe4e0aed..9c0fa8a54d 100644
--- a/packages/client/src/pages/settings/apps.vue
+++ b/packages/client/src/pages/settings/apps.vue
@@ -1,5 +1,5 @@
<template>
-<FormBase>
+<div class="_formRoot">
<FormPagination ref="list" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
@@ -8,7 +8,7 @@
</div>
</template>
<template v-slot="{items}">
- <div v-for="token in items" :key="token.id" class="_debobigegoPanel bfomjevm">
+ <div v-for="token in items" :key="token.id" class="_panel bfomjevm">
<img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/>
<div class="body">
<div class="name">{{ token.name }}</div>
@@ -34,23 +34,17 @@
</div>
</template>
</FormPagination>
-</FormBase>
+</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormPagination from '@/components/debobigego/pagination.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
+import FormPagination from '@/components/ui/pagination.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
FormPagination,
},
@@ -64,7 +58,7 @@ export default defineComponent({
bg: 'var(--bg)',
},
pagination: {
- endpoint: 'i/apps',
+ endpoint: 'i/apps' as const,
limit: 100,
params: {
sort: '+lastUsedAt'
@@ -73,10 +67,6 @@ export default defineComponent({
};
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
revoke(token) {
os.api('i/revoke-token', { tokenId: token.id }).then(() => {
diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue
index 155956923c..556ee30c1d 100644
--- a/packages/client/src/pages/settings/custom-css.vue
+++ b/packages/client/src/pages/settings/custom-css.vue
@@ -1,25 +1,18 @@
<template>
-<FormBase>
- <FormInfo warn>{{ $ts.customCssWarn }}</FormInfo>
+<div class="_formRoot">
+ <FormInfo warn class="_formBlock">{{ $ts.customCssWarn }}</FormInfo>
- <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;">
- <span>{{ $ts.local }}</span>
+ <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace _formBlock" style="tab-size: 2;">
+ <template #label>CSS</template>
</FormTextarea>
-</FormBase>
+</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormTextarea from '@/components/form/textarea.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormRadios from '@/components/form/radios.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormInfo from '@/components/debobigego/info.vue';
+import FormInfo from '@/components/ui/info.vue';
import * as os from '@/os';
-import { ColdDeviceStorage } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
import * as symbols from '@/symbols';
import { defaultStore } from '@/store';
@@ -27,12 +20,6 @@ import { defaultStore } from '@/store';
export default defineComponent({
components: {
FormTextarea,
- FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
- FormLink,
- FormButton,
FormInfo,
},
@@ -50,8 +37,6 @@ export default defineComponent({
},
mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
this.$watch('localCustomCss', this.apply);
},
diff --git a/packages/client/src/pages/settings/deck.vue b/packages/client/src/pages/settings/deck.vue
index bc82b0ca84..46b90d3d1a 100644
--- a/packages/client/src/pages/settings/deck.vue
+++ b/packages/client/src/pages/settings/deck.vue
@@ -1,42 +1,41 @@
<template>
-<FormBase>
+<div class="_formRoot">
<FormGroup>
<template #label>{{ $ts.defaultNavigationBehaviour }}</template>
<FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch>
</FormGroup>
- <FormSwitch v-model="alwaysShowMainColumn">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch>
+ <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch>
- <FormRadios v-model="columnAlign">
- <template #desc>{{ $ts._deck.columnAlign }}</template>
+ <FormRadios v-model="columnAlign" class="_formBlock">
+ <template #label>{{ $ts._deck.columnAlign }}</template>
<option value="left">{{ $ts.left }}</option>
<option value="center">{{ $ts.center }}</option>
</FormRadios>
- <FormRadios v-model="columnHeaderHeight">
- <template #desc>{{ $ts._deck.columnHeaderHeight }}</template>
+ <FormRadios v-model="columnHeaderHeight" class="_formBlock">
+ <template #label>{{ $ts._deck.columnHeaderHeight }}</template>
<option :value="42">{{ $ts.narrow }}</option>
<option :value="45">{{ $ts.medium }}</option>
<option :value="48">{{ $ts.wide }}</option>
</FormRadios>
- <FormInput v-model="columnMargin" type="number">
- <span>{{ $ts._deck.columnMargin }}</span>
+ <FormInput v-model="columnMargin" type="number" class="_formBlock">
+ <template #label>{{ $ts._deck.columnMargin }}</template>
<template #suffix>px</template>
</FormInput>
- <FormLink @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
-</FormBase>
+ <FormLink class="_formBlock" @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
+</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormRadios from '@/components/debobigego/radios.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormLink from '@/components/form/link.vue';
+import FormRadios from '@/components/form/radios.vue';
+import FormInput from '@/components/form/input.vue';
+import FormGroup from '@/components/form/group.vue';
import { deckStore } from '@/ui/deck/deck-store';
import * as os from '@/os';
import { unisonReload } from '@/scripts/unison-reload';
@@ -48,7 +47,6 @@ export default defineComponent({
FormLink,
FormInput,
FormRadios,
- FormBase,
FormGroup,
},
@@ -85,10 +83,6 @@ export default defineComponent({
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async setProfile() {
const { canceled, result: name } = await os.inputText({
diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue
index 6ce8d6509c..7edc81a309 100644
--- a/packages/client/src/pages/settings/delete-account.vue
+++ b/packages/client/src/pages/settings/delete-account.vue
@@ -1,28 +1,23 @@
<template>
-<FormBase>
- <FormInfo warn>{{ $ts._accountDelete.mayTakeTime }}</FormInfo>
- <FormInfo>{{ $ts._accountDelete.sendEmail }}</FormInfo>
- <FormButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton>
+<div class="_formRoot">
+ <FormInfo warn class="_formBlock">{{ $ts._accountDelete.mayTakeTime }}</FormInfo>
+ <FormInfo class="_formBlock">{{ $ts._accountDelete.sendEmail }}</FormInfo>
+ <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton>
<FormButton v-else disabled>{{ $ts._accountDelete.inProgress }}</FormButton>
-</FormBase>
+</div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
+import FormInfo from '@/components/ui/info.vue';
+import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
-import { debug } from '@/config';
import { signout } from '@/account';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
FormButton,
- FormGroup,
FormInfo,
},
@@ -35,14 +30,9 @@ export default defineComponent({
icon: 'fas fa-exclamation-triangle',
bg: 'var(--bg)',
},
- debug,
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async deleteAccount() {
{
diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue
index 9ab99c6efe..f1016ebd84 100644
--- a/packages/client/src/pages/settings/drive.vue
+++ b/packages/client/src/pages/settings/drive.vue
@@ -5,7 +5,7 @@
<div class="_formBlock uawsfosz">
<div class="meter"><div :style="meterStyle"></div></div>
</div>
- <div class="_inputSplit _formBlock">
+ <FormSplit>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.capacity }}</template>
<template #value>{{ bytes(capacity, 1) }}</template>
@@ -14,7 +14,7 @@
<template #key>{{ $ts.inUse }}</template>
<template #value>{{ bytes(usage, 1) }}</template>
</MkKeyValue>
- </div>
+ </FormSplit>
</FormSection>
<FormSection>
@@ -38,6 +38,7 @@ import * as tinycolor from 'tinycolor2';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/key-value.vue';
+import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import bytes from '@/filters/bytes';
import * as symbols from '@/symbols';
@@ -49,6 +50,7 @@ export default defineComponent({
FormLink,
FormSection,
MkKeyValue,
+ FormSplit,
},
emits: ['info'],
@@ -97,10 +99,6 @@ export default defineComponent({
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
chooseUploadFolder() {
os.selectDriveFolder(false).then(async folder => {
diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue
index b04295cce0..54557f8773 100644
--- a/packages/client/src/pages/settings/email.vue
+++ b/packages/client/src/pages/settings/email.vue
@@ -41,8 +41,6 @@
<script lang="ts">
import { defineComponent, onMounted, ref, watch } from 'vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormLink from '@/components/debobigego/link.vue';
import FormSection from '@/components/form/section.vue';
import FormInput from '@/components/form/input.vue';
import FormSwitch from '@/components/form/switch.vue';
@@ -54,8 +52,6 @@ import { i18n } from '@/i18n';
export default defineComponent({
components: {
FormSection,
- FormLink,
- FormButton,
FormSwitch,
FormInput,
},
@@ -115,8 +111,6 @@ export default defineComponent({
});
onMounted(() => {
- context.emit('info', INFO);
-
watch(emailAddress, () => {
saveEmailAddress();
});
diff --git a/packages/client/src/pages/settings/experimental-features.vue b/packages/client/src/pages/settings/experimental-features.vue
deleted file mode 100644
index 5a7bcb3b41..0000000000
--- a/packages/client/src/pages/settings/experimental-features.vue
+++ /dev/null
@@ -1,52 +0,0 @@
-<template>
-<FormBase>
- <FormButton @click="error()">error test</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormSwitch from '@/components/form/switch.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import * as os from '@/os';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSelect,
- FormSwitch,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.experimentalFeatures,
- icon: 'fas fa-flask'
- },
- stats: null
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- error() {
- throw new Error('Test error');
- }
- }
-});
-</script>
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index 734bc78442..2e159e56a9 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -195,10 +195,6 @@ export default defineComponent({
},
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async reloadAsk() {
const { canceled } = await os.confirm({
diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue
index a1dd6a1539..21031c559e 100644
--- a/packages/client/src/pages/settings/import-export.vue
+++ b/packages/client/src/pages/settings/import-export.vue
@@ -133,10 +133,6 @@ export default defineComponent({
os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
};
- onMounted(() => {
- context.emit('info', INFO);
- });
-
return {
[symbols.PAGE_INFO]: INFO,
excludeMutingUsers,
diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue
index 8ffff86705..66c8b147bb 100644
--- a/packages/client/src/pages/settings/index.vue
+++ b/packages/client/src/pages/settings/index.vue
@@ -14,7 +14,7 @@
</div>
<div class="main">
<div class="bkzroven">
- <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/>
+ <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/>
</div>
</div>
</div>
@@ -215,19 +215,9 @@ export default defineComponent({
case 'deck': return defineAsyncComponent(() => import('./deck.vue'));
case 'plugin': return defineAsyncComponent(() => import('./plugin.vue'));
case 'plugin/install': return defineAsyncComponent(() => import('./plugin.install.vue'));
- case 'plugin/manage': return defineAsyncComponent(() => import('./plugin.manage.vue'));
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
case 'account-info': return defineAsyncComponent(() => import('./account-info.vue'));
- case 'update': return defineAsyncComponent(() => import('./update.vue'));
- case 'registry': return defineAsyncComponent(() => import('./registry.vue'));
case 'delete-account': return defineAsyncComponent(() => import('./delete-account.vue'));
- case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue'));
- }
- if (page.value.startsWith('registry/keys/system/')) {
- return defineAsyncComponent(() => import('./registry.keys.vue'));
- }
- if (page.value.startsWith('registry/value/system/')) {
- return defineAsyncComponent(() => import('./registry.value.vue'));
}
return null;
});
@@ -235,17 +225,6 @@ export default defineComponent({
watch(component, () => {
pageProps.value = {};
- if (page.value) {
- if (page.value.startsWith('registry/keys/system/')) {
- pageProps.value.scope = page.value.replace('registry/keys/system/', '').split('/');
- }
- if (page.value.startsWith('registry/value/system/')) {
- const path = page.value.replace('registry/value/system/', '').split('/');
- pageProps.value.xKey = path.pop();
- pageProps.value.scope = path;
- }
- }
-
nextTick(() => {
scroll(el.value, { top: 0 });
});
@@ -271,8 +250,9 @@ export default defineComponent({
const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
- const onInfo = (info) => {
- childInfo.value = info;
+ const pageChanged = (page) => {
+ if (page == null) return;
+ childInfo.value = page[symbols.PAGE_INFO];
};
return {
@@ -285,7 +265,7 @@ export default defineComponent({
pageProps,
component,
emailNotConfigured,
- onInfo,
+ pageChanged,
childInfo,
};
},
diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue
index 584a21e4bd..f84a209b60 100644
--- a/packages/client/src/pages/settings/instance-mute.vue
+++ b/packages/client/src/pages/settings/instance-mute.vue
@@ -47,11 +47,6 @@ export default defineComponent({
},
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
-
async created() {
this.instanceMutes = this.$i.mutedInstances.join('\n');
},
diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue
index 3d8aaf8a6f..ca36c91665 100644
--- a/packages/client/src/pages/settings/integration.vue
+++ b/packages/client/src/pages/settings/integration.vue
@@ -1,45 +1,39 @@
<template>
-<FormBase>
- <div v-if="enableTwitterIntegration" class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fab fa-twitter"></i> Twitter</div>
- <div class="_debobigegoPanel" style="padding: 16px;">
- <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
- <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton>
- <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton>
- </div>
- </div>
+<div class="_formRoot">
+ <FormSection v-if="enableTwitterIntegration">
+ <template #label><i class="fab fa-twitter"></i> Twitter</template>
+ <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
+ <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton>
+ <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton>
+ </FormSection>
- <div v-if="enableDiscordIntegration" class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fab fa-discord"></i> Discord</div>
- <div class="_debobigegoPanel" style="padding: 16px;">
- <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p>
- <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton>
- <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton>
- </div>
- </div>
+ <FormSection v-if="enableDiscordIntegration">
+ <template #label><i class="fab fa-discord"></i> Discord</template>
+ <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p>
+ <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton>
+ <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton>
+ </FormSection>
- <div v-if="enableGithubIntegration" class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fab fa-github"></i> GitHub</div>
- <div class="_debobigegoPanel" style="padding: 16px;">
- <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p>
- <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton>
- <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton>
- </div>
- </div>
-</FormBase>
+ <FormSection v-if="enableGithubIntegration">
+ <template #label><i class="fab fa-github"></i> GitHub</template>
+ <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p>
+ <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton>
+ <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton>
+ </FormSection>
+</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { apiUrl } from '@/config';
-import FormBase from '@/components/debobigego/base.vue';
+import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
+ FormSection,
MkButton
},
@@ -79,8 +73,6 @@ export default defineComponent({
},
mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
document.cookie = `igi=${this.$i.token}; path=/;` +
` max-age=31536000;` +
(document.location.protocol.startsWith('https') ? ' secure' : '');
diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue
index 19d26be89a..6e38cd5dfe 100644
--- a/packages/client/src/pages/settings/menu.vue
+++ b/packages/client/src/pages/settings/menu.vue
@@ -21,7 +21,6 @@
import { defineComponent } from 'vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormRadios from '@/components/form/radios.vue';
-import FormBase from '@/components/debobigego/base.vue';
import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import { menuDef } from '@/menu';
@@ -31,7 +30,6 @@ import { unisonReload } from '@/scripts/unison-reload';
export default defineComponent({
components: {
- FormBase,
FormButton,
FormTextarea,
FormRadios,
@@ -69,10 +67,6 @@ export default defineComponent({
},
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async addItem() {
const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k));
diff --git a/packages/client/src/pages/settings/mute-block.vue b/packages/client/src/pages/settings/mute-block.vue
index 4f42d5e429..f4f9ebf8dd 100644
--- a/packages/client/src/pages/settings/mute-block.vue
+++ b/packages/client/src/pages/settings/mute-block.vue
@@ -1,5 +1,5 @@
<template>
-<FormBase>
+<div class="_formRoot">
<MkTab v-model="tab" style="margin-bottom: var(--margin);">
<option value="mute">{{ $ts.mutedUsers }}</option>
<option value="block">{{ $ts.blockedUsers }}</option>
@@ -8,11 +8,9 @@
<MkPagination :pagination="mutingPagination" class="muting">
<template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
<template v-slot="{items}">
- <FormGroup>
- <FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)">
- <MkAcct :user="mute.mutee"/>
- </FormLink>
- </FormGroup>
+ <FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)">
+ <MkAcct :user="mute.mutee"/>
+ </FormLink>
</template>
</MkPagination>
</div>
@@ -20,66 +18,43 @@
<MkPagination :pagination="blockingPagination" class="blocking">
<template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
<template v-slot="{items}">
- <FormGroup>
- <FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)">
- <MkAcct :user="block.blockee"/>
- </FormLink>
- </FormGroup>
+ <FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)">
+ <MkAcct :user="block.blockee"/>
+ </FormLink>
</template>
</MkPagination>
</div>
-</FormBase>
+</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import MkTab from '@/components/tab.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
+import FormInfo from '@/components/ui/info.vue';
+import FormLink from '@/components/form/link.vue';
import { userPage } from '@/filters/user';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkPagination,
- MkTab,
- FormInfo,
- FormBase,
- FormGroup,
- FormLink,
- },
+let tab = $ref('mute');
- emits: ['info'],
+const mutingPagination = {
+ endpoint: 'mute/list' as const,
+ limit: 10,
+};
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.muteAndBlock,
- icon: 'fas fa-ban',
- bg: 'var(--bg)',
- },
- tab: 'mute',
- mutingPagination: {
- endpoint: 'mute/list',
- limit: 10,
- },
- blockingPagination: {
- endpoint: 'blocking/list',
- limit: 10,
- },
- }
- },
+const blockingPagination = {
+ endpoint: 'blocking/list' as const,
+ limit: 10,
+};
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.muteAndBlock,
+ icon: 'fas fa-ban',
+ bg: 'var(--bg)',
},
-
- methods: {
- userPage
- }
});
</script>
diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue
index d3ada0d7ef..12171530bb 100644
--- a/packages/client/src/pages/settings/notifications.vue
+++ b/packages/client/src/pages/settings/notifications.vue
@@ -13,7 +13,6 @@
import { defineComponent } from 'vue';
import FormButton from '@/components/ui/button.vue';
import FormLink from '@/components/form/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
import FormSection from '@/components/form/section.vue';
import { notificationTypes } from 'misskey-js';
import * as os from '@/os';
@@ -21,7 +20,6 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
FormLink,
FormButton,
FormSection,
@@ -39,10 +37,6 @@ export default defineComponent({
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
readAllUnreadNotes() {
os.api('i/read-all-unread-notes');
diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue
index 0d9e60e21d..6e48cb58a6 100644
--- a/packages/client/src/pages/settings/other.vue
+++ b/packages/client/src/pages/settings/other.vue
@@ -1,30 +1,12 @@
<template>
<div class="_formRoot">
- <FormLink to="/settings/update" class="_formBlock">Misskey Update</FormLink>
-
- <FormSwitch :value="$i.injectFeaturedNote" @update:modelValue="onChangeInjectFeaturedNote" class="_formBlock">
+ <FormSwitch :value="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote">
{{ $ts.showFeaturedNotesInTimeline }}
</FormSwitch>
- <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #desc>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch>
+ <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #caption>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch>
<FormLink to="/settings/account-info" class="_formBlock">{{ $ts.accountInfo }}</FormLink>
- <FormLink to="/settings/experimental-features" class="_formBlock">{{ $ts.experimentalFeatures }}</FormLink>
-
- <FormSection>
- <template #label>{{ $ts.developer }}</template>
- <FormSwitch v-model="debug" @update:modelValue="changeDebug" class="_formBlock">
- DEBUG MODE
- </FormSwitch>
- <template v-if="debug">
- <FormButton @click="taskmanager">Task Manager</FormButton>
- </template>
- </FormSection>
-
- <FormLink to="/settings/registry" class="_formBlock"><template #icon><i class="fas fa-cogs"></i></template>{{ $ts.registry }}</FormLink>
-
- <FormLink to="/bios" behavior="browser" class="_formBlock"><template #icon><i class="fas fa-door-open"></i></template>BIOS</FormLink>
- <FormLink to="/cli" behavior="browser" class="_formBlock"><template #icon><i class="fas fa-door-open"></i></template>CLI</FormLink>
<FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink>
</div>
@@ -33,10 +15,8 @@
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
-import FormSelect from '@/components/form/select.vue';
import FormSection from '@/components/form/section.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormButton from '@/components/debobigego/button.vue';
+import FormLink from '@/components/form/link.vue';
import * as os from '@/os';
import { debug } from '@/config';
import { defaultStore } from '@/store';
@@ -45,10 +25,8 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormSelect,
FormSection,
FormSwitch,
- FormButton,
FormLink,
},
@@ -69,10 +47,6 @@ export default defineComponent({
reportError: defaultStore.makeGetterSetter('reportError'),
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
changeDebug(v) {
console.log(v);
@@ -85,11 +59,6 @@ export default defineComponent({
injectFeaturedNote: v
});
},
-
- taskmanager() {
- os.popup(import('@/components/taskmanager.vue'), {
- }, {}, 'closed');
- },
}
});
</script>
diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue
index af93ef2930..d35d20d17a 100644
--- a/packages/client/src/pages/settings/plugin.install.vue
+++ b/packages/client/src/pages/settings/plugin.install.vue
@@ -1,15 +1,15 @@
<template>
-<FormBase>
- <FormInfo warn>{{ $ts._plugin.installWarn }}</FormInfo>
+<div class="_formRoot">
+ <FormInfo warn class="_formBlock">{{ $ts._plugin.installWarn }}</FormInfo>
- <FormGroup>
- <FormTextarea v-model="code" tall>
- <span>{{ $ts.code }}</span>
- </FormTextarea>
- </FormGroup>
+ <FormTextarea v-model="code" tall class="_formBlock">
+ <template #label>{{ $ts.code }}</template>
+ </FormTextarea>
- <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton>
-</FormBase>
+ <div class="_formBlock">
+ <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton>
+ </div>
+</div>
</template>
<script lang="ts">
@@ -18,13 +18,8 @@ import { AiScript, parse } from '@syuilo/aiscript';
import { serialize } from '@syuilo/aiscript/built/serializer';
import { v4 as uuid } from 'uuid';
import FormTextarea from '@/components/form/textarea.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormRadios from '@/components/form/radios.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormInfo from '@/components/debobigego/info.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormInfo from '@/components/ui/info.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
@@ -33,11 +28,6 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormTextarea,
- FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
- FormLink,
FormButton,
FormInfo,
},
@@ -55,10 +45,6 @@ export default defineComponent({
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
installPlugin({ id, meta, ast, token }) {
ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({
diff --git a/packages/client/src/pages/settings/plugin.manage.vue b/packages/client/src/pages/settings/plugin.manage.vue
deleted file mode 100644
index 8b9021dc3d..0000000000
--- a/packages/client/src/pages/settings/plugin.manage.vue
+++ /dev/null
@@ -1,116 +0,0 @@
-<template>
-<FormBase>
- <FormGroup v-for="plugin in plugins" :key="plugin.id">
- <template #label><span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span></template>
-
- <FormSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch>
- <div class="_debobigegoItem">
- <div class="_debobigegoPanel" style="padding: 16px;">
- <div class="_keyValue">
- <div>{{ $ts.author }}:</div>
- <div>{{ plugin.author }}</div>
- </div>
- <div class="_keyValue">
- <div>{{ $ts.description }}:</div>
- <div>{{ plugin.description }}</div>
- </div>
- <div class="_keyValue">
- <div>{{ $ts.permission }}:</div>
- <div>{{ plugin.permissions }}</div>
- </div>
- </div>
- </div>
- <div class="_debobigegoItem">
- <div class="_debobigegoPanel" style="padding: 16px;">
- <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton>
- <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton>
- </div>
- </div>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@/components/ui/button.vue';
-import MkTextarea from '@/components/form/textarea.vue';
-import MkSelect from '@/components/form/select.vue';
-import FormSwitch from '@/components/form/switch.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import * as os from '@/os';
-import { ColdDeviceStorage } from '@/store';
-import * as symbols from '@/symbols';
-import { unisonReload } from '@/scripts/unison-reload';
-
-export default defineComponent({
- components: {
- MkButton,
- MkTextarea,
- MkSelect,
- FormSwitch,
- FormBase,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts._plugin.manage,
- icon: 'fas fa-plug',
- bg: 'var(--bg)',
- },
- plugins: ColdDeviceStorage.get('plugins'),
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- uninstall(plugin) {
- ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id));
- os.success();
- this.$nextTick(() => {
- unisonReload();
- });
- },
-
- // TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする
- async config(plugin) {
- const config = plugin.config;
- for (const key in plugin.configData) {
- config[key].default = plugin.configData[key];
- }
-
- const { canceled, result } = await os.form(plugin.name, config);
- if (canceled) return;
-
- const plugins = ColdDeviceStorage.get('plugins');
- plugins.find(p => p.id === plugin.id).configData = result;
- ColdDeviceStorage.set('plugins', plugins);
-
- this.$nextTick(() => {
- location.reload();
- });
- },
-
- changeActive(plugin, active) {
- const plugins = ColdDeviceStorage.get('plugins');
- plugins.find(p => p.id === plugin.id).active = active;
- ColdDeviceStorage.set('plugins', plugins);
-
- this.$nextTick(() => {
- location.reload();
- });
- }
- },
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue
index 50e53f459f..7a3ab9d152 100644
--- a/packages/client/src/pages/settings/plugin.vue
+++ b/packages/client/src/pages/settings/plugin.vue
@@ -1,23 +1,54 @@
<template>
-<FormBase>
+<div class="_formRoot">
<FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._plugin.install }}</FormLink>
- <FormLink to="/settings/plugin/manage"><template #icon><i class="fas fa-folder-open"></i></template>{{ $ts._plugin.manage }}<template #suffix>{{ plugins }}</template></FormLink>
-</FormBase>
+
+ <FormSection>
+ <template #label>{{ $ts.manage }}</template>
+ <div v-for="plugin in plugins" :key="plugin.id" class="_formBlock _panel" style="padding: 20px;">
+ <span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
+
+ <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch>
+
+ <MkKeyValue class="_formBlock">
+ <template #key>{{ $ts.author }}</template>
+ <template #value>{{ plugin.author }}</template>
+ </MkKeyValue>
+ <MkKeyValue class="_formBlock">
+ <template #key>{{ $ts.description }}</template>
+ <template #value>{{ plugin.description }}</template>
+ </MkKeyValue>
+ <MkKeyValue class="_formBlock">
+ <template #key>{{ $ts.permission }}</template>
+ <template #value>{{ plugin.permission }}</template>
+ </MkKeyValue>
+
+ <div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+ <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton>
+ <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton>
+ </div>
+ </div>
+ </FormSection>
+</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormLink from '@/components/debobigego/link.vue';
+import FormLink from '@/components/form/link.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormSection from '@/components/form/section.vue';
+import MkButton from '@/components/ui/button.vue';
+import MkKeyValue from '@/components/key-value.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
FormLink,
+ FormSwitch,
+ FormSection,
+ MkButton,
+ MkKeyValue,
},
emits: ['info'],
@@ -29,12 +60,47 @@ export default defineComponent({
icon: 'fas fa-plug',
bg: 'var(--bg)',
},
- plugins: ColdDeviceStorage.get('plugins').length,
+ plugins: ColdDeviceStorage.get('plugins'),
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
+ methods: {
+ uninstall(plugin) {
+ ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id));
+ os.success();
+ this.$nextTick(() => {
+ unisonReload();
+ });
+ },
+
+ // TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする
+ async config(plugin) {
+ const config = plugin.config;
+ for (const key in plugin.configData) {
+ config[key].default = plugin.configData[key];
+ }
+
+ const { canceled, result } = await os.form(plugin.name, config);
+ if (canceled) return;
+
+ const plugins = ColdDeviceStorage.get('plugins');
+ plugins.find(p => p.id === plugin.id).configData = result;
+ ColdDeviceStorage.set('plugins', plugins);
+
+ this.$nextTick(() => {
+ location.reload();
+ });
+ },
+
+ changeActive(plugin, active) {
+ const plugins = ColdDeviceStorage.get('plugins');
+ plugins.find(p => p.id === plugin.id).active = active;
+ ColdDeviceStorage.set('plugins', plugins);
+
+ this.$nextTick(() => {
+ location.reload();
+ });
+ }
},
});
</script>
diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue
index 78a0ea8b8d..dd13ba4bd0 100644
--- a/packages/client/src/pages/settings/privacy.vue
+++ b/packages/client/src/pages/settings/privacy.vue
@@ -47,8 +47,8 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormSection from '@/components/form/section.vue';
@@ -56,67 +56,39 @@ import FormGroup from '@/components/form/group.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
+import { $i } from '@/account';
-export default defineComponent({
- components: {
- FormSelect,
- FormSection,
- FormGroup,
- FormSwitch,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.privacy,
- icon: 'fas fa-lock-open',
- bg: 'var(--bg)',
- },
- isLocked: false,
- autoAcceptFollowed: false,
- noCrawle: false,
- isExplorable: false,
- hideOnlineStatus: false,
- publicReactions: false,
- ffVisibility: 'public',
- }
- },
+let isLocked = $ref($i.isLocked);
+let autoAcceptFollowed = $ref($i.autoAcceptFollowed);
+let noCrawle = $ref($i.noCrawle);
+let isExplorable = $ref($i.isExplorable);
+let hideOnlineStatus = $ref($i.hideOnlineStatus);
+let publicReactions = $ref($i.publicReactions);
+let ffVisibility = $ref($i.ffVisibility);
- computed: {
- defaultNoteVisibility: defaultStore.makeGetterSetter('defaultNoteVisibility'),
- defaultNoteLocalOnly: defaultStore.makeGetterSetter('defaultNoteLocalOnly'),
- rememberNoteVisibility: defaultStore.makeGetterSetter('rememberNoteVisibility'),
- keepCw: defaultStore.makeGetterSetter('keepCw'),
- },
+let defaultNoteVisibility = $computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
+let defaultNoteLocalOnly = $computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
+let rememberNoteVisibility = $computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
+let keepCw = $computed(defaultStore.makeGetterSetter('keepCw'));
- created() {
- this.isLocked = this.$i.isLocked;
- this.autoAcceptFollowed = this.$i.autoAcceptFollowed;
- this.noCrawle = this.$i.noCrawle;
- this.isExplorable = this.$i.isExplorable;
- this.hideOnlineStatus = this.$i.hideOnlineStatus;
- this.publicReactions = this.$i.publicReactions;
- this.ffVisibility = this.$i.ffVisibility;
- },
+function save() {
+ os.api('i/update', {
+ isLocked: !!isLocked,
+ autoAcceptFollowed: !!autoAcceptFollowed,
+ noCrawle: !!noCrawle,
+ isExplorable: !!isExplorable,
+ hideOnlineStatus: !!hideOnlineStatus,
+ publicReactions: !!publicReactions,
+ ffVisibility: ffVisibility,
+ });
+}
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.privacy,
+ icon: 'fas fa-lock-open',
+ bg: 'var(--bg)',
},
-
- methods: {
- save() {
- os.api('i/update', {
- isLocked: !!this.isLocked,
- autoAcceptFollowed: !!this.autoAcceptFollowed,
- noCrawle: !!this.noCrawle,
- isExplorable: !!this.isExplorable,
- hideOnlineStatus: !!this.hideOnlineStatus,
- publicReactions: !!this.publicReactions,
- ffVisibility: this.ffVisibility,
- });
- }
- }
});
</script>
diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue
index 2eaf9a9f83..f875146a2c 100644
--- a/packages/client/src/pages/settings/profile.vue
+++ b/packages/client/src/pages/settings/profile.vue
@@ -3,50 +3,50 @@
<div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
<div class="avatar _acrylic">
<MkAvatar class="avatar" :user="$i" :disable-link="true" @click="changeAvatar"/>
- <MkButton primary class="avatarEdit" @click="changeAvatar">{{ $ts._profile.changeAvatar }}</MkButton>
+ <MkButton primary class="avatarEdit" @click="changeAvatar">{{ i18n.locale._profile.changeAvatar }}</MkButton>
</div>
- <MkButton primary class="bannerEdit" @click="changeBanner">{{ $ts._profile.changeBanner }}</MkButton>
+ <MkButton primary class="bannerEdit" @click="changeBanner">{{ i18n.locale._profile.changeBanner }}</MkButton>
</div>
- <FormInput v-model="name" :max="30" manual-save class="_formBlock">
- <template #label>{{ $ts._profile.name }}</template>
+ <FormInput v-model="profile.name" :max="30" manual-save class="_formBlock">
+ <template #label>{{ i18n.locale._profile.name }}</template>
</FormInput>
- <FormTextarea v-model="description" :max="500" tall manual-save class="_formBlock">
- <template #label>{{ $ts._profile.description }}</template>
- <template #caption>{{ $ts._profile.youCanIncludeHashtags }}</template>
+ <FormTextarea v-model="profile.description" :max="500" tall manual-save class="_formBlock">
+ <template #label>{{ i18n.locale._profile.description }}</template>
+ <template #caption>{{ i18n.locale._profile.youCanIncludeHashtags }}</template>
</FormTextarea>
- <FormInput v-model="location" manual-save class="_formBlock">
- <template #label>{{ $ts.location }}</template>
+ <FormInput v-model="profile.location" manual-save class="_formBlock">
+ <template #label>{{ i18n.locale.location }}</template>
<template #prefix><i class="fas fa-map-marker-alt"></i></template>
</FormInput>
- <FormInput v-model="birthday" type="date" manual-save class="_formBlock">
- <template #label>{{ $ts.birthday }}</template>
+ <FormInput v-model="profile.birthday" type="date" manual-save class="_formBlock">
+ <template #label>{{ i18n.locale.birthday }}</template>
<template #prefix><i class="fas fa-birthday-cake"></i></template>
</FormInput>
- <FormSelect v-model="lang" class="_formBlock">
- <template #label>{{ $ts.language }}</template>
+ <FormSelect v-model="profile.lang" class="_formBlock">
+ <template #label>{{ i18n.locale.language }}</template>
<option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option>
</FormSelect>
<FormSlot>
- <MkButton @click="editMetadata">{{ $ts._profile.metadataEdit }}</MkButton>
- <template #caption>{{ $ts._profile.metadataDescription }}</template>
+ <MkButton @click="editMetadata">{{ i18n.locale._profile.metadataEdit }}</MkButton>
+ <template #caption>{{ i18n.locale._profile.metadataDescription }}</template>
</FormSlot>
- <FormSwitch v-model="isCat" class="_formBlock">{{ $ts.flagAsCat }}<template #caption>{{ $ts.flagAsCatDescription }}</template></FormSwitch>
+ <FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.locale.flagAsCat }}<template #caption>{{ i18n.locale.flagAsCatDescription }}</template></FormSwitch>
- <FormSwitch v-model="isBot" class="_formBlock">{{ $ts.flagAsBot }}<template #caption>{{ $ts.flagAsBotDescription }}</template></FormSwitch>
+ <FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.locale.flagAsBot }}<template #caption>{{ i18n.locale.flagAsBotDescription }}</template></FormSwitch>
- <FormSwitch v-model="alwaysMarkNsfw" class="_formBlock">{{ $ts.alwaysMarkSensitive }}</FormSwitch>
+ <FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.locale.alwaysMarkSensitive }}</FormSwitch>
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineComponent, reactive, watch } from 'vue';
import MkButton from '@/components/ui/button.vue';
import FormInput from '@/components/form/input.vue';
import FormTextarea from '@/components/form/textarea.vue';
@@ -57,198 +57,149 @@ import { host, langs } from '@/config';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
+import { $i } from '@/account';
-export default defineComponent({
- components: {
- MkButton,
- FormInput,
- FormTextarea,
- FormSwitch,
- FormSelect,
- FormSlot,
- },
-
- emits: ['info'],
+const profile = reactive({
+ name: $i.name,
+ description: $i.description,
+ location: $i.location,
+ birthday: $i.birthday,
+ lang: $i.lang,
+ isBot: $i.isBot,
+ isCat: $i.isCat,
+ alwaysMarkNsfw: $i.alwaysMarkNsfw,
+});
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.profile,
- icon: 'fas fa-user',
- bg: 'var(--bg)',
- },
- host,
- langs,
- name: null,
- description: null,
- birthday: null,
- lang: null,
- location: null,
- fieldName0: null,
- fieldValue0: null,
- fieldName1: null,
- fieldValue1: null,
- fieldName2: null,
- fieldValue2: null,
- fieldName3: null,
- fieldValue3: null,
- avatarId: null,
- bannerId: null,
- isBot: false,
- isCat: false,
- alwaysMarkNsfw: false,
- saving: false,
- }
- },
+const additionalFields = reactive({
+ fieldName0: $i.fields[0] ? $i.fields[0].name : null,
+ fieldValue0: $i.fields[0] ? $i.fields[0].value : null,
+ fieldName1: $i.fields[1] ? $i.fields[1].name : null,
+ fieldValue1: $i.fields[1] ? $i.fields[1].value : null,
+ fieldName2: $i.fields[2] ? $i.fields[2].name : null,
+ fieldValue2: $i.fields[2] ? $i.fields[2].value : null,
+ fieldName3: $i.fields[3] ? $i.fields[3].name : null,
+ fieldValue3: $i.fields[3] ? $i.fields[3].value : null,
+});
- created() {
- this.name = this.$i.name;
- this.description = this.$i.description;
- this.location = this.$i.location;
- this.birthday = this.$i.birthday;
- this.lang = this.$i.lang;
- this.avatarId = this.$i.avatarId;
- this.bannerId = this.$i.bannerId;
- this.isBot = this.$i.isBot;
- this.isCat = this.$i.isCat;
- this.alwaysMarkNsfw = this.$i.alwaysMarkNsfw;
+watch(() => profile, () => {
+ save();
+}, {
+ deep: true,
+});
- this.fieldName0 = this.$i.fields[0] ? this.$i.fields[0].name : null;
- this.fieldValue0 = this.$i.fields[0] ? this.$i.fields[0].value : null;
- this.fieldName1 = this.$i.fields[1] ? this.$i.fields[1].name : null;
- this.fieldValue1 = this.$i.fields[1] ? this.$i.fields[1].value : null;
- this.fieldName2 = this.$i.fields[2] ? this.$i.fields[2].name : null;
- this.fieldValue2 = this.$i.fields[2] ? this.$i.fields[2].value : null;
- this.fieldName3 = this.$i.fields[3] ? this.$i.fields[3].name : null;
- this.fieldValue3 = this.$i.fields[3] ? this.$i.fields[3].value : null;
+function save() {
+ os.apiWithDialog('i/update', {
+ name: profile.name || null,
+ description: profile.description || null,
+ location: profile.location || null,
+ birthday: profile.birthday || null,
+ lang: profile.lang || null,
+ isBot: !!profile.isBot,
+ isCat: !!profile.isCat,
+ alwaysMarkNsfw: !!profile.alwaysMarkNsfw,
+ });
+}
- this.$watch('name', this.save);
- this.$watch('description', this.save);
- this.$watch('location', this.save);
- this.$watch('birthday', this.save);
- this.$watch('lang', this.save);
- this.$watch('isBot', this.save);
- this.$watch('isCat', this.save);
- this.$watch('alwaysMarkNsfw', this.save);
- },
+function changeAvatar(ev) {
+ selectFile(ev.currentTarget || ev.target, i18n.locale.avatar).then(async (file) => {
+ const i = await os.apiWithDialog('i/update', {
+ avatarId: file.id,
+ });
+ $i.avatarId = i.avatarId;
+ $i.avatarUrl = i.avatarUrl;
+ });
+}
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
+function changeBanner(ev) {
+ selectFile(ev.currentTarget || ev.target, i18n.locale.banner).then(async (file) => {
+ const i = await os.apiWithDialog('i/update', {
+ bannerId: file.id,
+ });
+ $i.bannerId = i.bannerId;
+ $i.bannerUrl = i.bannerUrl;
+ });
+}
- methods: {
- changeAvatar(e) {
- selectFile(e.currentTarget || e.target, this.$ts.avatar).then(file => {
- os.api('i/update', {
- avatarId: file.id,
- });
- });
+async function editMetadata() {
+ const { canceled, result } = await os.form(i18n.locale._profile.metadata, {
+ fieldName0: {
+ type: 'string',
+ label: i18n.locale._profile.metadataLabel + ' 1',
+ default: additionalFields.fieldName0,
},
-
- changeBanner(e) {
- selectFile(e.currentTarget || e.target, this.$ts.banner).then(file => {
- os.api('i/update', {
- bannerId: file.id,
- });
- });
+ fieldValue0: {
+ type: 'string',
+ label: i18n.locale._profile.metadataContent + ' 1',
+ default: additionalFields.fieldValue0,
},
+ fieldName1: {
+ type: 'string',
+ label: i18n.locale._profile.metadataLabel + ' 2',
+ default: additionalFields.fieldName1,
+ },
+ fieldValue1: {
+ type: 'string',
+ label: i18n.locale._profile.metadataContent + ' 2',
+ default: additionalFields.fieldValue1,
+ },
+ fieldName2: {
+ type: 'string',
+ label: i18n.locale._profile.metadataLabel + ' 3',
+ default: additionalFields.fieldName2,
+ },
+ fieldValue2: {
+ type: 'string',
+ label: i18n.locale._profile.metadataContent + ' 3',
+ default: additionalFields.fieldValue2,
+ },
+ fieldName3: {
+ type: 'string',
+ label: i18n.locale._profile.metadataLabel + ' 4',
+ default: additionalFields.fieldName3,
+ },
+ fieldValue3: {
+ type: 'string',
+ label: i18n.locale._profile.metadataContent + ' 4',
+ default: additionalFields.fieldValue3,
+ },
+ });
+ if (canceled) return;
- async editMetadata() {
- const { canceled, result } = await os.form(this.$ts._profile.metadata, {
- fieldName0: {
- type: 'string',
- label: this.$ts._profile.metadataLabel + ' 1',
- default: this.fieldName0,
- },
- fieldValue0: {
- type: 'string',
- label: this.$ts._profile.metadataContent + ' 1',
- default: this.fieldValue0,
- },
- fieldName1: {
- type: 'string',
- label: this.$ts._profile.metadataLabel + ' 2',
- default: this.fieldName1,
- },
- fieldValue1: {
- type: 'string',
- label: this.$ts._profile.metadataContent + ' 2',
- default: this.fieldValue1,
- },
- fieldName2: {
- type: 'string',
- label: this.$ts._profile.metadataLabel + ' 3',
- default: this.fieldName2,
- },
- fieldValue2: {
- type: 'string',
- label: this.$ts._profile.metadataContent + ' 3',
- default: this.fieldValue2,
- },
- fieldName3: {
- type: 'string',
- label: this.$ts._profile.metadataLabel + ' 4',
- default: this.fieldName3,
- },
- fieldValue3: {
- type: 'string',
- label: this.$ts._profile.metadataContent + ' 4',
- default: this.fieldValue3,
- },
- });
- if (canceled) return;
-
- this.fieldName0 = result.fieldName0;
- this.fieldValue0 = result.fieldValue0;
- this.fieldName1 = result.fieldName1;
- this.fieldValue1 = result.fieldValue1;
- this.fieldName2 = result.fieldName2;
- this.fieldValue2 = result.fieldValue2;
- this.fieldName3 = result.fieldName3;
- this.fieldValue3 = result.fieldValue3;
-
- const fields = [
- { name: this.fieldName0, value: this.fieldValue0 },
- { name: this.fieldName1, value: this.fieldValue1 },
- { name: this.fieldName2, value: this.fieldValue2 },
- { name: this.fieldName3, value: this.fieldValue3 },
- ];
+ additionalFields.fieldName0 = result.fieldName0;
+ additionalFields.fieldValue0 = result.fieldValue0;
+ additionalFields.fieldName1 = result.fieldName1;
+ additionalFields.fieldValue1 = result.fieldValue1;
+ additionalFields.fieldName2 = result.fieldName2;
+ additionalFields.fieldValue2 = result.fieldValue2;
+ additionalFields.fieldName3 = result.fieldName3;
+ additionalFields.fieldValue3 = result.fieldValue3;
- os.api('i/update', {
- fields,
- }).then(i => {
- os.success();
- }).catch(err => {
- os.alert({
- type: 'error',
- text: err.id
- });
- });
- },
+ const fields = [
+ { name: additionalFields.fieldName0, value: additionalFields.fieldValue0 },
+ { name: additionalFields.fieldName1, value: additionalFields.fieldValue1 },
+ { name: additionalFields.fieldName2, value: additionalFields.fieldValue2 },
+ { name: additionalFields.fieldName3, value: additionalFields.fieldValue3 },
+ ];
- save() {
- this.saving = true;
+ os.api('i/update', {
+ fields,
+ }).then(i => {
+ os.success();
+ }).catch(err => {
+ os.alert({
+ type: 'error',
+ text: err.id
+ });
+ });
+}
- os.apiWithDialog('i/update', {
- name: this.name || null,
- description: this.description || null,
- location: this.location || null,
- birthday: this.birthday || null,
- lang: this.lang || null,
- isBot: !!this.isBot,
- isCat: !!this.isCat,
- alwaysMarkNsfw: !!this.alwaysMarkNsfw,
- }).then(i => {
- this.saving = false;
- this.$i.avatarId = i.avatarId;
- this.$i.avatarUrl = i.avatarUrl;
- this.$i.bannerId = i.bannerId;
- this.$i.bannerUrl = i.bannerUrl;
- }).catch(err => {
- this.saving = false;
- });
- },
- }
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.profile,
+ icon: 'fas fa-user',
+ bg: 'var(--bg)',
+ },
});
</script>
diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue
index 0d4db46936..e5b1189947 100644
--- a/packages/client/src/pages/settings/reaction.vue
+++ b/packages/client/src/pages/settings/reaction.vue
@@ -100,10 +100,6 @@ export default defineComponent({
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
save() {
this.$store.set('reactions', this.reactions);
diff --git a/packages/client/src/pages/settings/registry.keys.vue b/packages/client/src/pages/settings/registry.keys.vue
deleted file mode 100644
index 89953ebea1..0000000000
--- a/packages/client/src/pages/settings/registry.keys.vue
+++ /dev/null
@@ -1,114 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts._registry.domain }}</template>
- <template #value>{{ $ts.system }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts._registry.scope }}</template>
- <template #value>{{ scope.join('/') }}</template>
- </FormKeyValueView>
- </FormGroup>
-
- <FormGroup v-if="keys">
- <template #label>{{ $ts._registry.keys }}</template>
- <FormLink v-for="key in keys" :to="`/settings/registry/value/system/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink>
- </FormGroup>
-
- <FormButton primary @click="createKey">{{ $ts._registry.createKey }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import FormSwitch from '@/components/form/switch.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import * as os from '@/os';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSelect,
- FormSwitch,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- },
-
- props: {
- scope: {
- required: true
- }
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.registry,
- icon: 'fas fa-cogs',
- bg: 'var(--bg)',
- },
- keys: null,
- }
- },
-
- watch: {
- scope() {
- this.fetch();
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- this.fetch();
- },
-
- methods: {
- fetch() {
- os.api('i/registry/keys-with-type', {
- scope: this.scope
- }).then(keys => {
- this.keys = Object.entries(keys).sort((a, b) => a[0].localeCompare(b[0]));
- });
- },
-
- async createKey() {
- const { canceled, result } = await os.form(this.$ts._registry.createKey, {
- key: {
- type: 'string',
- label: this.$ts._registry.key,
- },
- value: {
- type: 'string',
- multiline: true,
- label: this.$ts.value,
- },
- scope: {
- type: 'string',
- label: this.$ts._registry.scope,
- default: this.scope.join('/')
- }
- });
- if (canceled) return;
- os.apiWithDialog('i/registry/set', {
- scope: result.scope.split('/'),
- key: result.key,
- value: JSON5.parse(result.value),
- }).then(() => {
- this.fetch();
- });
- }
- }
-});
-</script>
diff --git a/packages/client/src/pages/settings/registry.value.vue b/packages/client/src/pages/settings/registry.value.vue
deleted file mode 100644
index 6acd3f6048..0000000000
--- a/packages/client/src/pages/settings/registry.value.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<template>
-<FormBase>
- <FormInfo warn>{{ $ts.editTheseSettingsMayBreakAccount }}</FormInfo>
-
- <template v-if="value">
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts._registry.domain }}</template>
- <template #value>{{ $ts.system }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts._registry.scope }}</template>
- <template #value>{{ scope.join('/') }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts._registry.key }}</template>
- <template #value>{{ xKey }}</template>
- </FormKeyValueView>
- </FormGroup>
-
- <FormGroup>
- <FormTextarea v-model="valueForEditor" tall class="_monospace" style="tab-size: 2;">
- <span>{{ $ts.value }} (JSON)</span>
- </FormTextarea>
- <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormGroup>
-
- <FormKeyValueView>
- <template #key>{{ $ts.updatedAt }}</template>
- <template #value><MkTime :time="value.updatedAt" mode="detail"/></template>
- </FormKeyValueView>
-
- <FormButton danger @click="del"><i class="fas fa-trash"></i> {{ $ts.delete }}</FormButton>
- </template>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import FormInfo from '@/components/debobigego/info.vue';
-import FormSwitch from '@/components/form/switch.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormTextarea from '@/components/form/textarea.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import * as os from '@/os';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- FormInfo,
- FormBase,
- FormSelect,
- FormSwitch,
- FormButton,
- FormTextarea,
- FormGroup,
- FormKeyValueView,
- },
-
- props: {
- scope: {
- required: true
- },
- xKey: {
- required: true
- },
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.registry,
- icon: 'fas fa-cogs',
- bg: 'var(--bg)',
- },
- value: null,
- valueForEditor: null,
- }
- },
-
- watch: {
- key() {
- this.fetch();
- },
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- this.fetch();
- },
-
- methods: {
- fetch() {
- os.api('i/registry/get-detail', {
- scope: this.scope,
- key: this.xKey
- }).then(value => {
- this.value = value;
- this.valueForEditor = JSON5.stringify(this.value.value, null, '\t');
- });
- },
-
- save() {
- try {
- JSON5.parse(this.valueForEditor);
- } catch (e) {
- os.alert({
- type: 'error',
- text: this.$ts.invalidValue
- });
- return;
- }
-
- os.confirm({
- type: 'warning',
- text: this.$ts.saveConfirm,
- }).then(({ canceled }) => {
- if (canceled) return;
- os.apiWithDialog('i/registry/set', {
- scope: this.scope,
- key: this.xKey,
- value: JSON5.parse(this.valueForEditor)
- });
- });
- },
-
- del() {
- os.confirm({
- type: 'warning',
- text: this.$ts.deleteConfirm,
- }).then(({ canceled }) => {
- if (canceled) return;
- os.apiWithDialog('i/registry/remove', {
- scope: this.scope,
- key: this.xKey
- });
- });
- }
- }
-});
-</script>
diff --git a/packages/client/src/pages/settings/registry.vue b/packages/client/src/pages/settings/registry.vue
deleted file mode 100644
index 6faff5d2a4..0000000000
--- a/packages/client/src/pages/settings/registry.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-<template>
-<FormBase>
- <FormGroup v-if="scopes">
- <template #label>{{ $ts.system }}</template>
- <FormLink v-for="scope in scopes" :to="`/settings/registry/keys/system/${scope.join('/')}`" class="_monospace">{{ scope.join('/') }}</FormLink>
- </FormGroup>
- <FormButton primary @click="createKey">{{ $ts._registry.createKey }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import FormSwitch from '@/components/form/switch.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import * as os from '@/os';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSelect,
- FormSwitch,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.registry,
- icon: 'fas fa-cogs',
- bg: 'var(--bg)',
- },
- scopes: null,
- }
- },
-
- created() {
- this.fetch();
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- fetch() {
- os.api('i/registry/scopes').then(scopes => {
- this.scopes = scopes.slice().sort((a, b) => a.join('/').localeCompare(b.join('/')));
- });
- },
-
- async createKey() {
- const { canceled, result } = await os.form(this.$ts._registry.createKey, {
- key: {
- type: 'string',
- label: this.$ts._registry.key,
- },
- value: {
- type: 'string',
- multiline: true,
- label: this.$ts.value,
- },
- scope: {
- type: 'string',
- label: this.$ts._registry.scope,
- }
- });
- if (canceled) return;
- os.apiWithDialog('i/registry/set', {
- scope: result.scope.split('/'),
- key: result.key,
- value: JSON5.parse(result.value),
- }).then(() => {
- this.fetch();
- });
- }
- }
-});
-</script>
diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue
index 069f9d964d..6fb3f1c413 100644
--- a/packages/client/src/pages/settings/security.vue
+++ b/packages/client/src/pages/settings/security.vue
@@ -12,7 +12,7 @@
<FormSection>
<template #label>{{ $ts.signinHistory }}</template>
- <FormPagination :pagination="pagination">
+ <MkPagination :pagination="pagination">
<template v-slot="{items}">
<div>
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
@@ -25,7 +25,7 @@
</div>
</div>
</template>
- </FormPagination>
+ </MkPagination>
</FormSection>
<FormSection>
@@ -40,10 +40,9 @@
<script lang="ts">
import { defineComponent } from 'vue';
import FormSection from '@/components/form/section.vue';
-import FormLink from '@/components/debobigego/link.vue';
import FormSlot from '@/components/form/slot.vue';
import FormButton from '@/components/ui/button.vue';
-import FormPagination from '@/components/form/pagination.vue';
+import MkPagination from '@/components/ui/pagination.vue';
import X2fa from './2fa.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
@@ -51,9 +50,8 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormSection,
- FormLink,
FormButton,
- FormPagination,
+ MkPagination,
FormSlot,
X2fa,
},
@@ -68,16 +66,12 @@ export default defineComponent({
bg: 'var(--bg)',
},
pagination: {
- endpoint: 'i/signin-history',
+ endpoint: 'i/signin-history' as const,
limit: 5,
},
}
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async change() {
const { canceled: canceled1, result: currentPassword } = await os.inputText({
diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue
index 0977dd8322..490a1b5514 100644
--- a/packages/client/src/pages/settings/sounds.vue
+++ b/packages/client/src/pages/settings/sounds.vue
@@ -94,12 +94,6 @@ export default defineComponent({
this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg');
this.sounds.antenna = ColdDeviceStorage.get('sound_antenna');
this.sounds.channel = ColdDeviceStorage.get('sound_channel');
- this.sounds.reversiPutBlack = ColdDeviceStorage.get('sound_reversiPutBlack');
- this.sounds.reversiPutWhite = ColdDeviceStorage.get('sound_reversiPutWhite');
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue
index c3e531afb2..e2a3f042b9 100644
--- a/packages/client/src/pages/settings/theme.install.vue
+++ b/packages/client/src/pages/settings/theme.install.vue
@@ -1,105 +1,79 @@
<template>
-<FormBase>
- <FormGroup>
- <FormTextarea v-model="installThemeCode">
- <span>{{ $ts._theme.code }}</span>
- </FormTextarea>
- <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton>
- </FormGroup>
+<div class="_formRoot">
+ <FormTextarea v-model="installThemeCode" class="_formBlock">
+ <template #label>{{ i18n.locale._theme.code }}</template>
+ </FormTextarea>
- <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton>
-</FormBase>
+ <div class="_formBlock" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+ <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ i18n.locale.preview }}</FormButton>
+ <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ i18n.locale.install }}</FormButton>
+ </div>
+</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
import * as JSON5 from 'json5';
import FormTextarea from '@/components/form/textarea.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormRadios from '@/components/form/radios.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormButton from '@/components/debobigego/button.vue';
+import FormButton from '@/components/ui/button.vue';
import { applyTheme, validateTheme } from '@/scripts/theme';
import * as os from '@/os';
-import { ColdDeviceStorage } from '@/store';
import { addTheme, getThemes } from '@/theme-store';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- FormTextarea,
- FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
- FormLink,
- FormButton,
- },
-
- emits: ['info'],
+let installThemeCode = $ref(null);
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts._theme.install,
- icon: 'fas fa-download',
- bg: 'var(--bg)',
- },
- installThemeCode: null,
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
+function parseThemeCode(code: string) {
+ let theme;
- methods: {
- parseThemeCode(code) {
- let theme;
+ try {
+ theme = JSON5.parse(code);
+ } catch (e) {
+ os.alert({
+ type: 'error',
+ text: i18n.locale._theme.invalid
+ });
+ return false;
+ }
+ if (!validateTheme(theme)) {
+ os.alert({
+ type: 'error',
+ text: i18n.locale._theme.invalid
+ });
+ return false;
+ }
+ if (getThemes().some(t => t.id === theme.id)) {
+ os.alert({
+ type: 'info',
+ text: i18n.locale._theme.alreadyInstalled
+ });
+ return false;
+ }
- try {
- theme = JSON5.parse(code);
- } catch (e) {
- os.alert({
- type: 'error',
- text: this.$ts._theme.invalid
- });
- return false;
- }
- if (!validateTheme(theme)) {
- os.alert({
- type: 'error',
- text: this.$ts._theme.invalid
- });
- return false;
- }
- if (getThemes().some(t => t.id === theme.id)) {
- os.alert({
- type: 'info',
- text: this.$ts._theme.alreadyInstalled
- });
- return false;
- }
+ return theme;
+}
- return theme;
- },
+function preview(code: string): void {
+ const theme = parseThemeCode(code);
+ if (theme) applyTheme(theme, false);
+}
- preview(code) {
- const theme = this.parseThemeCode(code);
- if (theme) applyTheme(theme, false);
- },
+async function install(code: string): Promise<void> {
+ const theme = parseThemeCode(code);
+ if (!theme) return;
+ await addTheme(theme);
+ os.alert({
+ type: 'success',
+ text: i18n.t('_theme.installed', { name: theme.name })
+ });
+}
- async install(code) {
- const theme = this.parseThemeCode(code);
- if (!theme) return;
- await addTheme(theme);
- os.alert({
- type: 'success',
- text: this.$t('_theme.installed', { name: theme.name })
- });
- },
- }
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale._theme.install,
+ icon: 'fas fa-download',
+ bg: 'var(--bg)',
+ },
});
</script>
diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue
index c605b1eb64..a1e849b540 100644
--- a/packages/client/src/pages/settings/theme.manage.vue
+++ b/packages/client/src/pages/settings/theme.manage.vue
@@ -30,9 +30,6 @@ import { defineComponent } from 'vue';
import * as JSON5 from 'json5';
import FormTextarea from '@/components/form/textarea.vue';
import FormSelect from '@/components/form/select.vue';
-import FormRadios from '@/components/form/radios.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import { Theme, builtinThemes } from '@/scripts/theme';
@@ -46,9 +43,6 @@ export default defineComponent({
components: {
FormTextarea,
FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
FormInput,
FormButton,
},
@@ -84,10 +78,6 @@ export default defineComponent({
},
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
copyThemeCode() {
copyToClipboard(this.selectedThemeCode);
diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index 6c88b65699..658e36ec05 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -163,10 +163,6 @@ export default defineComponent({
location.reload();
});
- onMounted(() => {
- emit('info', INFO);
- });
-
onActivated(() => {
fetchThemes().then(() => {
installedThemes.value = getThemes();
diff --git a/packages/client/src/pages/settings/update.vue b/packages/client/src/pages/settings/update.vue
deleted file mode 100644
index e0d8f6d15c..0000000000
--- a/packages/client/src/pages/settings/update.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-<template>
-<FormBase>
- <template v-if="meta">
- <FormInfo v-if="version === meta.version">{{ $ts.youAreRunningUpToDateClient }}</FormInfo>
- <FormInfo v-else warn>{{ $ts.newVersionOfClientAvailable }}</FormInfo>
- </template>
- <FormGroup>
- <template #label>{{ instanceName }}</template>
- <FormKeyValueView>
- <template #key>{{ $ts.currentVersion }}</template>
- <template #value>{{ version }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.latestVersion }}</template>
- <template v-if="meta" #value>{{ meta.version }}</template>
- <template v-else #value><MkEllipsis/></template>
- </FormKeyValueView>
- </FormGroup>
- <FormGroup>
- <template #label>Misskey</template>
- <FormKeyValueView>
- <template #key>{{ $ts.latestVersion }}</template>
- <template v-if="releases" #value>{{ releases[0].tag_name }}</template>
- <template v-else #value><MkEllipsis/></template>
- </FormKeyValueView>
- <template v-if="releases" #caption><MkTime :time="releases[0].published_at" mode="detail"/></template>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormSwitch from '@/components/form/switch.vue';
-import FormSelect from '@/components/form/select.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import FormInfo from '@/components/debobigego/info.vue';
-import * as os from '@/os';
-import { version, instanceName } from '@/config';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSelect,
- FormSwitch,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- FormInfo,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'Misskey Update',
- icon: 'fas fa-sync-alt',
- bg: 'var(--bg)',
- },
- version,
- instanceName,
- releases: null,
- meta: null
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
- os.api('meta', {
- detail: false
- }).then(meta => {
- this.meta = meta;
- localStorage.setItem('v', meta.version);
- });
-
- fetch('https://api.github.com/repos/misskey-dev/misskey/releases', {
- method: 'GET',
- })
- .then(res => res.json())
- .then(res => {
- this.releases = res;
- });
- },
-
- methods: {
- }
-});
-</script>
diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue
index 068f88740a..19980dea14 100644
--- a/packages/client/src/pages/settings/word-mute.vue
+++ b/packages/client/src/pages/settings/word-mute.vue
@@ -31,7 +31,6 @@
<script lang="ts">
import { defineComponent } from 'vue';
import FormTextarea from '@/components/form/textarea.vue';
-import FormBase from '@/components/debobigego/base.vue';
import MkKeyValue from '@/components/key-value.vue';
import MkButton from '@/components/ui/button.vue';
import MkInfo from '@/components/ui/info.vue';
@@ -42,7 +41,6 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
- FormBase,
MkButton,
FormTextarea,
MkKeyValue,
@@ -89,10 +87,6 @@ export default defineComponent({
this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count;
},
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
methods: {
async save() {
this.$store.set('mutedWords', this.softMutedWords.trim().split('\n').map(x => x.trim().split(' ')));
diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue
index bdd8500ee4..5df6256fb2 100644
--- a/packages/client/src/pages/share.vue
+++ b/packages/client/src/pages/share.vue
@@ -169,7 +169,7 @@ export default defineComponent({
window.close();
// 閉じなければ100ms後タイムラインに
- setTimeout(() => {
+ window.setTimeout(() => {
this.$router.push('/');
}, 100);
}
diff --git a/packages/client/src/pages/signup-complete.vue b/packages/client/src/pages/signup-complete.vue
index 89375e05d2..a10af1a4cc 100644
--- a/packages/client/src/pages/signup-complete.vue
+++ b/packages/client/src/pages/signup-complete.vue
@@ -1,50 +1,36 @@
<template>
<div>
- {{ $ts.processing }}
+ {{ i18n.locale.processing }}
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted } from 'vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { login } from '@/account';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
+const props = defineProps<{
+ code: string;
+}>();
- },
-
- props: {
- code: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.signup,
- icon: 'fas fa-user'
- },
- }
- },
+onMounted(async () => {
+ await os.alert({
+ type: 'info',
+ text: i18n.t('clickToFinishEmailVerification', { ok: i18n.locale.gotIt }),
+ });
+ const res = await os.apiWithDialog('signup-pending', {
+ code: props.code,
+ });
+ login(res.i, '/');
+});
- async mounted() {
- await os.alert({
- type: 'info',
- text: this.$t('clickToFinishEmailVerification', { ok: this.$ts.gotIt }),
- });
- const res = await os.apiWithDialog('signup-pending', {
- code: this.code,
- });
- login(res.i, '/');
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.signup,
+ icon: 'fas fa-user',
},
-
- methods: {
-
- }
});
</script>
diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue
index a0c8367849..045f1ef259 100644
--- a/packages/client/src/pages/tag.vue
+++ b/packages/client/src/pages/tag.vue
@@ -1,46 +1,31 @@
<template>
<div class="_section">
- <XNotes ref="notes" class="_content" :pagination="pagination"/>
+ <XNotes class="_content" :pagination="pagination"/>
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
-export default defineComponent({
- components: {
- XNotes
- },
+const props = defineProps<{
+ tag: string;
+}>();
- props: {
- tag: {
- type: String,
- required: true
- }
- },
+const pagination = {
+ endpoint: 'notes/search-by-tag' as const,
+ limit: 10,
+ params: computed(() => ({
+ tag: props.tag,
+ })),
+};
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.tag,
- icon: 'fas fa-hashtag'
- },
- pagination: {
- endpoint: 'notes/search-by-tag',
- limit: 10,
- params: () => ({
- tag: this.tag,
- })
- },
- };
- },
-
- watch: {
- tag() {
- (this.$refs.notes as any).reload();
- }
- },
+defineExpose({
+ [symbols.PAGE_INFO]: computed(() => ({
+ title: props.tag,
+ icon: 'fas fa-hashtag',
+ bg: 'var(--bg)',
+ })),
});
</script>
diff --git a/packages/client/src/pages/test.vue b/packages/client/src/pages/test.vue
deleted file mode 100644
index d05e00d374..0000000000
--- a/packages/client/src/pages/test.vue
+++ /dev/null
@@ -1,260 +0,0 @@
-<template>
-<div class="_section">
- <div class="_content">
- <div class="_card _gap">
- <div class="_title">Dialog</div>
- <div class="_content">
- <MkInput v-model="dialogTitle">
- <template #label>Title</template>
- </MkInput>
- <MkInput v-model="dialogBody">
- <template #label>Body</template>
- </MkInput>
- <MkRadio v-model="dialogType" value="info">Info</MkRadio>
- <MkRadio v-model="dialogType" value="success">Success</MkRadio>
- <MkRadio v-model="dialogType" value="warning">Warn</MkRadio>
- <MkRadio v-model="dialogType" value="error">Error</MkRadio>
- <MkSwitch v-model="dialogCancel">
- <span>With cancel button</span>
- </MkSwitch>
- <MkSwitch v-model="dialogCancelByBgClick">
- <span>Can cancel by modal bg click</span>
- </MkSwitch>
- <MkSwitch v-model="dialogInput">
- <span>With input field</span>
- </MkSwitch>
- <MkButton @click="showDialog()">Show</MkButton>
- </div>
- <div class="_content">
- <code>Result: {{ dialogResult }}</code>
- </div>
- </div>
-
- <div class="_card _gap">
- <div class="_title">Form</div>
- <div class="_content">
- <MkInput v-model="formTitle">
- <template #label>Title</template>
- </MkInput>
- <MkTextarea v-model="formForm">
- <template #label>Form</template>
- </MkTextarea>
- <MkButton @click="form()">Show</MkButton>
- </div>
- <div class="_content">
- <code>Result: {{ formResult }}</code>
- </div>
- </div>
-
- <div class="_card _gap">
- <div class="_title">MFM</div>
- <div class="_content">
- <MkTextarea v-model="mfm">
- <template #label>MFM</template>
- </MkTextarea>
- </div>
- <div class="_content">
- <Mfm :text="mfm"/>
- </div>
- </div>
-
- <div class="_card _gap">
- <div class="_title">selectDriveFile</div>
- <div class="_content">
- <MkSwitch v-model="selectDriveFileMultiple">
- <span>Multiple</span>
- </MkSwitch>
- <MkButton @click="selectDriveFile()">selectDriveFile</MkButton>
- </div>
- <div class="_content">
- <code>Result: {{ JSON.stringify(selectDriveFileResult) }}</code>
- </div>
- </div>
-
- <div class="_card _gap">
- <div class="_title">selectDriveFolder</div>
- <div class="_content">
- <MkSwitch v-model="selectDriveFolderMultiple">
- <span>Multiple</span>
- </MkSwitch>
- <MkButton @click="selectDriveFolder()">selectDriveFolder</MkButton>
- </div>
- <div class="_content">
- <code>Result: {{ JSON.stringify(selectDriveFolderResult) }}</code>
- </div>
- </div>
-
- <div class="_card _gap">
- <div class="_title">selectUser</div>
- <div class="_content">
- <MkButton @click="selectUser()">selectUser</MkButton>
- </div>
- <div class="_content">
- <code>Result: {{ user }}</code>
- </div>
- </div>
-
- <div class="_card _gap">
- <div class="_title">Notification</div>
- <div class="_content">
- <MkInput v-model="notificationIconUrl">
- <template #label>Icon URL</template>
- </MkInput>
- <MkInput v-model="notificationHeader">
- <template #label>Header</template>
- </MkInput>
- <MkTextarea v-model="notificationBody">
- <template #label>Body</template>
- </MkTextarea>
- <MkButton @click="createNotification()">createNotification</MkButton>
- </div>
- </div>
-
- <div class="_card _gap">
- <div class="_title">Waiting dialog</div>
- <div class="_content">
- <MkButton inline @click="openWaitingDialog()">icon only</MkButton>
- <MkButton inline @click="openWaitingDialog('Doing')">with text</MkButton>
- </div>
- </div>
-
- <div class="_card _gap">
- <div class="_title">Messaging window</div>
- <div class="_content">
- <MkButton @click="messagingWindowOpen()">open</MkButton>
- </div>
- </div>
-
- <MkButton @click="resetTutorial()">Reset tutorial</MkButton>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent } from 'vue';
-import MkButton from '@/components/ui/button.vue';
-import MkInput from '@/components/form/input.vue';
-import MkSwitch from '@/components/form/switch.vue';
-import MkTextarea from '@/components/form/textarea.vue';
-import MkRadio from '@/components/form/radio.vue';
-import * as os from '@/os';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSwitch,
- MkTextarea,
- MkRadio,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'TEST',
- icon: 'fas fa-exclamation-triangle'
- },
- dialogTitle: 'Hello',
- dialogBody: 'World!',
- dialogType: 'info',
- dialogCancel: false,
- dialogCancelByBgClick: true,
- dialogInput: false,
- dialogResult: null,
- formTitle: 'Test form',
- formForm: JSON.stringify({
- foo: {
- type: 'boolean',
- default: true,
- label: 'This is a boolean property'
- },
- bar: {
- type: 'number',
- default: 300,
- label: 'This is a number property'
- },
- baz: {
- type: 'string',
- default: 'Misskey makes you happy.',
- label: 'This is a string property'
- },
- qux: {
- type: 'string',
- multiline: true,
- default: 'Misskey makes\nyou happy.',
- label: 'Multiline string'
- },
- }, null, '\t'),
- formResult: null,
- mfm: '',
- selectDriveFileMultiple: false,
- selectDriveFolderMultiple: false,
- selectDriveFileResult: null,
- selectDriveFolderResult: null,
- user: null,
- notificationIconUrl: null,
- notificationHeader: '',
- notificationBody: '',
- }
- },
-
- methods: {
- async showDialog() {
- this.dialogResult = null;
- /*
- this.dialogResult = await os.dialog({
- type: this.dialogType,
- title: this.dialogTitle,
- text: this.dialogBody,
- showCancelButton: this.dialogCancel,
- cancelableByBgClick: this.dialogCancelByBgClick,
- input: this.dialogInput ? {} : null
- });*/
- },
-
- async form() {
- this.formResult = null;
- this.formResult = await os.form(this.formTitle, JSON.parse(this.formForm));
- },
-
- async selectDriveFile() {
- this.selectDriveFileResult = null;
- this.selectDriveFileResult = await os.selectDriveFile(this.selectDriveFileMultiple);
- },
-
- async selectDriveFolder() {
- this.selectDriveFolderResult = null;
- this.selectDriveFolderResult = await os.selectDriveFolder(this.selectDriveFolderMultiple);
- },
-
- async selectUser() {
- this.user = null;
- this.user = await os.selectUser();
- },
-
- async createNotification() {
- os.api('notifications/create', {
- header: this.notificationHeader,
- body: this.notificationBody,
- icon: this.notificationIconUrl,
- });
- },
-
- messagingWindowOpen() {
- os.pageWindow('/my/messaging');
- },
-
- openWaitingDialog(text?) {
- const promise = new Promise((resolve, reject) => {
- setTimeout(resolve, 2000);
- });
- os.promiseDialog(promise, null, null, text);
- },
-
- resetTutorial() {
- this.$store.set('tutorial', 0);
- },
- }
-});
-</script>
diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index f023653425..80b8c7806c 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -1,300 +1,274 @@
<template>
-<FormBase class="cwepdizn">
- <div class="_debobigegoItem colorPicker">
- <div class="_debobigegoLabel">{{ $ts.backgroundColor }}</div>
- <div class="_debobigegoPanel colors">
- <div class="row">
- <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
- <div class="preview" :style="{ background: color.forPreview }"></div>
- </button>
+<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
+ <div class="cwepdizn _formRoot">
+ <FormFolder :default-open="true" class="_formBlock">
+ <template #label>{{ i18n.locale.backgroundColor }}</template>
+ <div class="cwepdizn-colors">
+ <div class="row">
+ <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
+ <div class="preview" :style="{ background: color.forPreview }"></div>
+ </button>
+ </div>
+ <div class="row">
+ <button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
+ <div class="preview" :style="{ background: color.forPreview }"></div>
+ </button>
+ </div>
</div>
- <div class="row">
- <button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
- <div class="preview" :style="{ background: color.forPreview }"></div>
- </button>
- </div>
- </div>
- </div>
- <div class="_debobigegoItem colorPicker">
- <div class="_debobigegoLabel">{{ $ts.accentColor }}</div>
- <div class="_debobigegoPanel colors">
- <div class="row">
- <button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)">
- <div class="preview" :style="{ background: color }"></div>
- </button>
+ </FormFolder>
+
+ <FormFolder :default-open="true" class="_formBlock">
+ <template #label>{{ i18n.locale.accentColor }}</template>
+ <div class="cwepdizn-colors">
+ <div class="row">
+ <button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)">
+ <div class="preview" :style="{ background: color }"></div>
+ </button>
+ </div>
</div>
- </div>
- </div>
- <div class="_debobigegoItem colorPicker">
- <div class="_debobigegoLabel">{{ $ts.textColor }}</div>
- <div class="_debobigegoPanel colors">
- <div class="row">
- <button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)">
- <div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div>
- </button>
+ </FormFolder>
+
+ <FormFolder :default-open="true" class="_formBlock">
+ <template #label>{{ i18n.locale.textColor }}</template>
+ <div class="cwepdizn-colors">
+ <div class="row">
+ <button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)">
+ <div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div>
+ </button>
+ </div>
</div>
- </div>
- </div>
+ </FormFolder>
- <FormGroup v-if="codeEnabled">
- <FormTextarea v-model="themeCode" tall>
- <span>{{ $ts._theme.code }}</span>
- </FormTextarea>
- <FormButton primary @click="applyThemeCode">{{ $ts.apply }}</FormButton>
- </FormGroup>
- <FormButton v-else @click="codeEnabled = true"><i class="fas fa-code"></i> {{ $ts.editCode }}</FormButton>
+ <FormFolder :default-open="false" class="_formBlock">
+ <template #icon><i class="fas fa-code"></i></template>
+ <template #label>{{ i18n.locale.editCode }}</template>
- <FormGroup v-if="descriptionEnabled">
- <FormTextarea v-model="description">
- <span>{{ $ts._theme.description }}</span>
- </FormTextarea>
- </FormGroup>
- <FormButton v-else @click="descriptionEnabled = true">{{ $ts.addDescription }}</FormButton>
+ <div class="_formRoot">
+ <FormTextarea v-model="themeCode" tall class="_formBlock">
+ <template #label>{{ i18n.locale._theme.code }}</template>
+ </FormTextarea>
+ <FormButton primary class="_formBlock" @click="applyThemeCode">{{ i18n.locale.apply }}</FormButton>
+ </div>
+ </FormFolder>
- <FormGroup>
- <FormButton @click="showPreview"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton>
- <FormButton primary @click="saveAs"><i class="fas fa-save"></i> {{ $ts.saveAs }}</FormButton>
- </FormGroup>
-</FormBase>
+ <FormFolder :default-open="false" class="_formBlock">
+ <template #label>{{ i18n.locale.addDescription }}</template>
+
+ <div class="_formRoot">
+ <FormTextarea v-model="description">
+ <template #label>{{ i18n.locale._theme.description }}</template>
+ </FormTextarea>
+ </div>
+ </FormFolder>
+ </div>
+</MkSpacer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { watch } from 'vue';
import { toUnicode } from 'punycode/';
import * as tinycolor from 'tinycolor2';
import { v4 as uuid} from 'uuid';
import * as JSON5 from 'json5';
-import FormBase from '@/components/debobigego/base.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormGroup from '@/components/debobigego/group.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormTextarea from '@/components/form/textarea.vue';
+import FormFolder from '@/components/form/folder.vue';
-import { Theme, applyTheme, validateTheme, darkTheme, lightTheme } from '@/scripts/theme';
+import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme';
import { host } from '@/config';
import * as os from '@/os';
-import { ColdDeviceStorage } from '@/store';
+import { ColdDeviceStorage, defaultStore } from '@/store';
import { addTheme } from '@/theme-store';
import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
+import { useLeaveGuard } from '@/scripts/use-leave-guard';
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- FormTextarea,
- FormGroup,
- },
+const bgColors = [
+ { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' },
+ { color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' },
+ { color: '#e9eff0', kind: 'light', forPreview: '#bfe3e8' },
+ { color: '#f0e9ee', kind: 'light', forPreview: '#f1d1e8' },
+ { color: '#dce2e0', kind: 'light', forPreview: '#a4dccc' },
+ { color: '#e2e0dc', kind: 'light', forPreview: '#d8c7a5' },
+ { color: '#d5dbe0', kind: 'light', forPreview: '#b0cae0' },
+ { color: '#dad5d5', kind: 'light', forPreview: '#d6afaf' },
+ { color: '#2b2b2b', kind: 'dark', forPreview: '#444444' },
+ { color: '#362e29', kind: 'dark', forPreview: '#735c4d' },
+ { color: '#303629', kind: 'dark', forPreview: '#506d2f' },
+ { color: '#293436', kind: 'dark', forPreview: '#258192' },
+ { color: '#2e2936', kind: 'dark', forPreview: '#504069' },
+ { color: '#252722', kind: 'dark', forPreview: '#3c462f' },
+ { color: '#212525', kind: 'dark', forPreview: '#303e3e' },
+ { color: '#191919', kind: 'dark', forPreview: '#272727' },
+] as const;
+const accentColors = ['#e36749', '#f29924', '#98c934', '#34c9a9', '#34a1c9', '#606df7', '#8d34c9', '#e84d83'];
+const fgColors = [
+ { color: 'none', forLight: '#5f5f5f', forDark: '#dadada', forPreview: null },
+ { color: 'red', forLight: '#7f6666', forDark: '#e4d1d1', forPreview: '#ca4343' },
+ { color: 'yellow', forLight: '#736955', forDark: '#e0d5c0', forPreview: '#d49923' },
+ { color: 'green', forLight: '#586d5b', forDark: '#d1e4d4', forPreview: '#4cbd5c' },
+ { color: 'cyan', forLight: '#5d7475', forDark: '#d1e3e4', forPreview: '#2abdc3' },
+ { color: 'blue', forLight: '#676880', forDark: '#d1d2e4', forPreview: '#7275d8' },
+ { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' },
+];
- async beforeRouteLeave(to, from) {
- if (this.changed && !(await this.leaveConfirm())) {
- return false;
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.themeEditor,
- icon: 'fas fa-palette',
- },
- theme: {
- base: 'light',
- props: lightTheme.props
- } as Theme,
- codeEnabled: false,
- descriptionEnabled: false,
- description: null,
- themeCode: null,
- bgColors: [
- { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' },
- { color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' },
- { color: '#e9eff0', kind: 'light', forPreview: '#bfe3e8' },
- { color: '#f0e9ee', kind: 'light', forPreview: '#f1d1e8' },
- { color: '#dce2e0', kind: 'light', forPreview: '#a4dccc' },
- { color: '#e2e0dc', kind: 'light', forPreview: '#d8c7a5' },
- { color: '#d5dbe0', kind: 'light', forPreview: '#b0cae0' },
- { color: '#dad5d5', kind: 'light', forPreview: '#d6afaf' },
- { color: '#2b2b2b', kind: 'dark', forPreview: '#444444' },
- { color: '#362e29', kind: 'dark', forPreview: '#735c4d' },
- { color: '#303629', kind: 'dark', forPreview: '#506d2f' },
- { color: '#293436', kind: 'dark', forPreview: '#258192' },
- { color: '#2e2936', kind: 'dark', forPreview: '#504069' },
- { color: '#252722', kind: 'dark', forPreview: '#3c462f' },
- { color: '#212525', kind: 'dark', forPreview: '#303e3e' },
- { color: '#191919', kind: 'dark', forPreview: '#272727' },
- ],
- accentColors: ['#e36749', '#f29924', '#98c934', '#34c9a9', '#34a1c9', '#606df7', '#8d34c9', '#e84d83'],
- fgColors: [
- { color: 'none', forLight: '#5f5f5f', forDark: '#dadada', forPreview: null },
- { color: 'red', forLight: '#7f6666', forDark: '#e4d1d1', forPreview: '#ca4343' },
- { color: 'yellow', forLight: '#736955', forDark: '#e0d5c0', forPreview: '#d49923' },
- { color: 'green', forLight: '#586d5b', forDark: '#d1e4d4', forPreview: '#4cbd5c' },
- { color: 'cyan', forLight: '#5d7475', forDark: '#d1e3e4', forPreview: '#2abdc3' },
- { color: 'blue', forLight: '#676880', forDark: '#d1d2e4', forPreview: '#7275d8' },
- { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' },
- ],
- changed: false,
- }
- },
-
- created() {
- this.$watch('theme', this.apply, { deep: true });
- window.addEventListener('beforeunload', this.beforeunload);
- },
+const theme = $ref<Partial<Theme>>({
+ base: 'light',
+ props: lightTheme.props,
+});
+let description = $ref<string | null>(null);
+let themeCode = $ref<string | null>(null);
+let changed = $ref(false);
- beforeUnmount() {
- window.removeEventListener('beforeunload', this.beforeunload);
- },
+useLeaveGuard($$(changed));
- methods: {
- beforeunload(e: BeforeUnloadEvent) {
- if (this.changed) {
- e.preventDefault();
- e.returnValue = '';
- }
- },
+function showPreview() {
+ os.pageWindow('preview');
+}
- async leaveConfirm(): Promise<boolean> {
- const { canceled } = await os.confirm({
- type: 'warning',
- text: this.$ts.leaveConfirm,
- });
- return !canceled;
- },
+function setBgColor(color: typeof bgColors[number]) {
+ if (theme.base != color.kind) {
+ const base = color.kind === 'dark' ? darkTheme : lightTheme;
+ for (const prop of Object.keys(base.props)) {
+ if (prop === 'accent') continue;
+ if (prop === 'fg') continue;
+ theme.props[prop] = base.props[prop];
+ }
+ }
+ theme.base = color.kind;
+ theme.props.bg = color.color;
- showPreview() {
- os.pageWindow('preview');
- },
+ if (theme.props.fg) {
+ const matchedFgColor = fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(theme.props.fg).toRgbString()));
+ if (matchedFgColor) setFgColor(matchedFgColor);
+ }
+}
- setBgColor(color) {
- if (this.theme.base != color.kind) {
- const base = color.kind === 'dark' ? darkTheme : lightTheme;
- for (const prop of Object.keys(base.props)) {
- if (prop === 'accent') continue;
- if (prop === 'fg') continue;
- this.theme.props[prop] = base.props[prop];
- }
- }
- this.theme.base = color.kind;
- this.theme.props.bg = color.color;
+function setAccentColor(color) {
+ theme.props.accent = color;
+}
- if (this.theme.props.fg) {
- const matchedFgColor = this.fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(this.theme.props.fg).toRgbString()));
- if (matchedFgColor) this.setFgColor(matchedFgColor);
- }
- },
+function setFgColor(color) {
+ theme.props.fg = theme.base === 'light' ? color.forLight : color.forDark;
+}
- setAccentColor(color) {
- this.theme.props.accent = color;
- },
+function apply() {
+ themeCode = JSON5.stringify(theme, null, '\t');
+ applyTheme(theme, false);
+ changed = true;
+}
- setFgColor(color) {
- this.theme.props.fg = this.theme.base === 'light' ? color.forLight : color.forDark;
- },
+function applyThemeCode() {
+ let parsed;
- apply() {
- this.themeCode = JSON5.stringify(this.theme, null, '\t');
- applyTheme(this.theme, false);
- this.changed = true;
- },
+ try {
+ parsed = JSON5.parse(themeCode);
+ } catch (err) {
+ os.alert({
+ type: 'error',
+ text: i18n.locale._theme.invalid,
+ });
+ return;
+ }
- applyThemeCode() {
- let parsed;
+ theme = parsed;
+}
- try {
- parsed = JSON5.parse(this.themeCode);
- } catch (e) {
- os.alert({
- type: 'error',
- text: this.$ts._theme.invalid
- });
- return;
- }
+async function saveAs() {
+ const { canceled, result: name } = await os.inputText({
+ title: i18n.locale.name,
+ allowEmpty: false,
+ });
+ if (canceled) return;
- this.theme = parsed;
- },
+ theme.id = uuid();
+ theme.name = name;
+ theme.author = `@${$i.username}@${toUnicode(host)}`;
+ if (description) theme.desc = description;
+ addTheme(theme);
+ applyTheme(theme);
+ if (defaultStore.state.darkMode) {
+ ColdDeviceStorage.set('darkTheme', theme);
+ } else {
+ ColdDeviceStorage.set('lightTheme', theme);
+ }
+ changed = false;
+ os.alert({
+ type: 'success',
+ text: i18n.t('_theme.installed', { name: theme.name }),
+ });
+}
- async saveAs() {
- const { canceled, result: name } = await os.inputText({
- title: this.$ts.name,
- allowEmpty: false
- });
- if (canceled) return;
+watch($$(theme), apply, { deep: true });
- this.theme.id = uuid();
- this.theme.name = name;
- this.theme.author = `@${this.$i.username}@${toUnicode(host)}`;
- if (this.description) this.theme.desc = this.description;
- addTheme(this.theme);
- applyTheme(this.theme);
- if (this.$store.state.darkMode) {
- ColdDeviceStorage.set('darkTheme', this.theme);
- } else {
- ColdDeviceStorage.set('lightTheme', this.theme);
- }
- this.changed = false;
- os.alert({
- type: 'success',
- text: this.$t('_theme.installed', { name: this.theme.name })
- });
- }
- }
+defineExpose({
+ [symbols.PAGE_INFO]: {
+ title: i18n.locale.themeEditor,
+ icon: 'fas fa-palette',
+ bg: 'var(--bg)',
+ actions: [{
+ asFullButton: true,
+ icon: 'fas fa-eye',
+ text: i18n.locale.preview,
+ handler: showPreview,
+ }, {
+ asFullButton: true,
+ icon: 'fas fa-check',
+ text: i18n.locale.saveAs,
+ handler: saveAs,
+ }],
+ },
});
</script>
<style lang="scss" scoped>
.cwepdizn {
- max-width: 800px;
- margin: 0 auto;
+ ::v-deep(.cwepdizn-colors) {
+ text-align: center;
- > .colorPicker {
- > .colors {
- padding: 32px;
- text-align: center;
+ > .row {
+ > .color {
+ display: inline-block;
+ position: relative;
+ width: 64px;
+ height: 64px;
+ border-radius: 8px;
- > .row {
- > .color {
- display: inline-block;
- position: relative;
- width: 64px;
- height: 64px;
- border-radius: 8px;
+ > .preview {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ margin: auto;
+ width: 42px;
+ height: 42px;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgb(0 0 0 / 30%);
+ transition: transform 0.15s ease;
+ }
+ &:hover {
> .preview {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- margin: auto;
- width: 42px;
- height: 42px;
- border-radius: 4px;
- box-shadow: 0 2px 4px rgb(0 0 0 / 30%);
- transition: transform 0.15s ease;
+ transform: scale(1.1);
}
+ }
- &:hover {
- > .preview {
- transform: scale(1.1);
- }
- }
+ &.active {
+ box-shadow: 0 0 0 2px var(--divider) inset;
+ }
- &.active {
- box-shadow: 0 0 0 2px var(--divider) inset;
- }
+ &.rounded {
+ border-radius: 999px;
- &.rounded {
+ > .preview {
border-radius: 999px;
-
- > .preview {
- border-radius: 999px;
- }
}
+ }
- &.char {
- line-height: 42px;
- }
+ &.char {
+ line-height: 42px;
}
}
}
diff --git a/packages/client/src/pages/timeline.tutorial.vue b/packages/client/src/pages/timeline.tutorial.vue
index 3775796940..432d28c60b 100644
--- a/packages/client/src/pages/timeline.tutorial.vue
+++ b/packages/client/src/pages/timeline.tutorial.vue
@@ -65,26 +65,14 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
import MkButton from '@/components/ui/button.vue';
+import { defaultStore } from '@/store';
-export default defineComponent({
- components: {
- MkButton,
- },
-
- data() {
- return {
- }
- },
-
- computed: {
- tutorial: {
- get() { return this.$store.reactiveState.tutorial.value || 0; },
- set(value) { this.$store.set('tutorial', value); }
- },
- },
+const tutorial = computed({
+ get() { return defaultStore.reactiveState.tutorial.value || 0; },
+ set(value) { defaultStore.set('tutorial', value); }
});
</script>
diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue
index 216b3c34ea..aabb953aec 100644
--- a/packages/client/src/pages/timeline.vue
+++ b/packages/client/src/pages/timeline.vue
@@ -1,6 +1,6 @@
<template>
<MkSpacer :content-max="800">
- <div v-hotkey.global="keymap" class="cmuxhskf">
+ <div ref="rootEl" v-hotkey.global="keymap" class="cmuxhskf">
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/>
@@ -18,162 +18,144 @@
</template>
<script lang="ts">
-import { defineComponent, defineAsyncComponent, computed } from 'vue';
+export default {
+ name: 'MkTimelinePage',
+}
+</script>
+
+<script lang="ts" setup>
+import { defineAsyncComponent, computed, watch } from 'vue';
import XTimeline from '@/components/timeline.vue';
import XPostForm from '@/components/post-form.vue';
import { scroll } from '@/scripts/scroll';
import * as os from '@/os';
import * as symbols from '@/symbols';
+import { defaultStore } from '@/store';
+import { i18n } from '@/i18n';
+import { instance } from '@/instance';
+import { $i } from '@/account';
-export default defineComponent({
- name: 'timeline',
-
- components: {
- XTimeline,
- XTutorial: defineAsyncComponent(() => import('./timeline.tutorial.vue')),
- XPostForm,
- },
+const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue'));
- data() {
- return {
- src: 'home',
- queue: 0,
- [symbols.PAGE_INFO]: computed(() => ({
- title: this.$ts.timeline,
- icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home',
- bg: 'var(--bg)',
- actions: [{
- icon: 'fas fa-list-ul',
- text: this.$ts.lists,
- handler: this.chooseList
- }, {
- icon: 'fas fa-satellite',
- text: this.$ts.antennas,
- handler: this.chooseAntenna
- }, {
- icon: 'fas fa-satellite-dish',
- text: this.$ts.channel,
- handler: this.chooseChannel
- }, {
- icon: 'fas fa-calendar-alt',
- text: this.$ts.jumpToSpecifiedDate,
- handler: this.timetravel
- }],
- tabs: [{
- active: this.src === 'home',
- title: this.$ts._timelines.home,
- icon: 'fas fa-home',
- iconOnly: true,
- onClick: () => { this.src = 'home'; this.saveSrc(); },
- }, ...(this.isLocalTimelineAvailable ? [{
- active: this.src === 'local',
- title: this.$ts._timelines.local,
- icon: 'fas fa-comments',
- iconOnly: true,
- onClick: () => { this.src = 'local'; this.saveSrc(); },
- }, {
- active: this.src === 'social',
- title: this.$ts._timelines.social,
- icon: 'fas fa-share-alt',
- iconOnly: true,
- onClick: () => { this.src = 'social'; this.saveSrc(); },
- }] : []), ...(this.isGlobalTimelineAvailable ? [{
- active: this.src === 'global',
- title: this.$ts._timelines.global,
- icon: 'fas fa-globe',
- iconOnly: true,
- onClick: () => { this.src = 'global'; this.saveSrc(); },
- }] : [])],
- })),
- };
- },
+const isLocalTimelineAvailable = !instance.disableLocalTimeline || ($i != null && ($i.isModerator || $i.isAdmin));
+const isGlobalTimelineAvailable = !instance.disableGlobalTimeline || ($i != null && ($i.isModerator || $i.isAdmin));
+const keymap = {
+ 't': focus,
+};
- computed: {
- keymap(): any {
- return {
- 't': this.focus
- };
- },
+const tlComponent = $ref<InstanceType<typeof XTimeline>>();
+const rootEl = $ref<HTMLElement>();
- isLocalTimelineAvailable(): boolean {
- return !this.$instance.disableLocalTimeline || this.$i.isModerator || this.$i.isAdmin;
- },
+let src = $ref<'home' | 'local' | 'social' | 'global'>(defaultStore.state.tl.src);
+let queue = $ref(0);
- isGlobalTimelineAvailable(): boolean {
- return !this.$instance.disableGlobalTimeline || this.$i.isModerator || this.$i.isAdmin;
- },
- },
-
- watch: {
- src() {
- this.showNav = false;
- },
- },
-
- created() {
- this.src = this.$store.state.tl.src;
- },
+function queueUpdated(q: number): void {
+ queue = q;
+}
- methods: {
- queueUpdated(q) {
- this.queue = q;
- },
+function top(): void {
+ scroll(rootEl, { top: 0 });
+}
- top() {
- scroll(this.$el, { top: 0 });
- },
+async function chooseList(ev: MouseEvent): Promise<void> {
+ const lists = await os.api('users/lists/list');
+ const items = lists.map(list => ({
+ type: 'link',
+ text: list.name,
+ to: `/timeline/list/${list.id}`,
+ }));
+ os.popupMenu(items, ev.currentTarget || ev.target);
+}
- async chooseList(ev) {
- const lists = await os.api('users/lists/list');
- const items = lists.map(list => ({
- type: 'link',
- text: list.name,
- to: `/timeline/list/${list.id}`
- }));
- os.popupMenu(items, ev.currentTarget || ev.target);
- },
+async function chooseAntenna(ev: MouseEvent): Promise<void> {
+ const antennas = await os.api('antennas/list');
+ const items = antennas.map(antenna => ({
+ type: 'link',
+ text: antenna.name,
+ indicate: antenna.hasUnreadNote,
+ to: `/timeline/antenna/${antenna.id}`,
+ }));
+ os.popupMenu(items, ev.currentTarget || ev.target);
+}
- async chooseAntenna(ev) {
- const antennas = await os.api('antennas/list');
- const items = antennas.map(antenna => ({
- type: 'link',
- text: antenna.name,
- indicate: antenna.hasUnreadNote,
- to: `/timeline/antenna/${antenna.id}`
- }));
- os.popupMenu(items, ev.currentTarget || ev.target);
- },
+async function chooseChannel(ev: MouseEvent): Promise<void> {
+ const channels = await os.api('channels/followed');
+ const items = channels.map(channel => ({
+ type: 'link',
+ text: channel.name,
+ indicate: channel.hasUnreadNote,
+ to: `/channels/${channel.id}`,
+ }));
+ os.popupMenu(items, ev.currentTarget || ev.target);
+}
- async chooseChannel(ev) {
- const channels = await os.api('channels/followed');
- const items = channels.map(channel => ({
- type: 'link',
- text: channel.name,
- indicate: channel.hasUnreadNote,
- to: `/channels/${channel.id}`
- }));
- os.popupMenu(items, ev.currentTarget || ev.target);
- },
+function saveSrc(): void {
+ defaultStore.set('tl', {
+ src: src,
+ });
+}
- saveSrc() {
- this.$store.set('tl', {
- src: this.src,
- });
- },
+async function timetravel(): Promise<void> {
+ const { canceled, result: date } = await os.inputDate({
+ title: i18n.locale.date,
+ });
+ if (canceled) return;
- async timetravel() {
- const { canceled, result: date } = await os.inputDate({
- title: this.$ts.date,
- });
- if (canceled) return;
+ tlComponent.timetravel(date);
+}
- this.$refs.tl.timetravel(date);
- },
+function focus(): void {
+ tlComponent.focus();
+}
- focus() {
- (this.$refs.tl as any).focus();
- }
- }
+defineExpose({
+ [symbols.PAGE_INFO]: computed(() => ({
+ title: i18n.locale.timeline,
+ icon: src === 'local' ? 'fas fa-comments' : src === 'social' ? 'fas fa-share-alt' : src === 'global' ? 'fas fa-globe' : 'fas fa-home',
+ bg: 'var(--bg)',
+ actions: [{
+ icon: 'fas fa-list-ul',
+ text: i18n.locale.lists,
+ handler: chooseList,
+ }, {
+ icon: 'fas fa-satellite',
+ text: i18n.locale.antennas,
+ handler: chooseAntenna,
+ }, {
+ icon: 'fas fa-satellite-dish',
+ text: i18n.locale.channel,
+ handler: chooseChannel,
+ }, {
+ icon: 'fas fa-calendar-alt',
+ text: i18n.locale.jumpToSpecifiedDate,
+ handler: timetravel,
+ }],
+ tabs: [{
+ active: src === 'home',
+ title: i18n.locale._timelines.home,
+ icon: 'fas fa-home',
+ iconOnly: true,
+ onClick: () => { src = 'home'; saveSrc(); },
+ }, ...(isLocalTimelineAvailable ? [{
+ active: src === 'local',
+ title: i18n.locale._timelines.local,
+ icon: 'fas fa-comments',
+ iconOnly: true,
+ onClick: () => { src = 'local'; saveSrc(); },
+ }, {
+ active: src === 'social',
+ title: i18n.locale._timelines.social,
+ icon: 'fas fa-share-alt',
+ iconOnly: true,
+ onClick: () => { src = 'social'; saveSrc(); },
+ }] : []), ...(isGlobalTimelineAvailable ? [{
+ active: src === 'global',
+ title: i18n.locale._timelines.global,
+ icon: 'fas fa-globe',
+ iconOnly: true,
+ onClick: () => { src = 'global'; saveSrc(); },
+ }] : [])],
+ })),
});
</script>
diff --git a/packages/client/src/pages/user-ap-info.vue b/packages/client/src/pages/user-ap-info.vue
deleted file mode 100644
index 0027381f53..0000000000
--- a/packages/client/src/pages/user-ap-info.vue
+++ /dev/null
@@ -1,124 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense v-slot="{ result: ap }" :p="apPromiseFactory">
- <FormGroup>
- <template #label>ActivityPub</template>
- <FormKeyValueView>
- <template #key>Type</template>
- <template #value><span class="_monospace">{{ ap.type }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>URI</template>
- <template #value><span class="_monospace">{{ ap.id }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>URL</template>
- <template #value><span class="_monospace">{{ ap.url }}</span></template>
- </FormKeyValueView>
- <FormGroup>
- <FormKeyValueView>
- <template #key>Inbox</template>
- <template #value><span class="_monospace">{{ ap.inbox }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>Shared Inbox</template>
- <template #value><span class="_monospace">{{ ap.sharedInbox || ap.endpoints.sharedInbox }}</span></template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>Outbox</template>
- <template #value><span class="_monospace">{{ ap.outbox }}</span></template>
- </FormKeyValueView>
- </FormGroup>
- <FormTextarea readonly tall code pre :value="ap.publicKey.publicKeyPem">
- <span>Public Key</span>
- </FormTextarea>
- <FormKeyValueView>
- <template #key>Discoverable</template>
- <template #value>{{ ap.discoverable ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>ManuallyApprovesFollowers</template>
- <template #value>{{ ap.manuallyApprovesFollowers ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormObjectView tall :value="ap">
- <span>Raw</span>
- </FormObjectView>
- <FormGroup>
- <FormLink :to="`https://${user.host}/.well-known/webfinger?resource=acct:${user.username}`" external>WebFinger</FormLink>
- </FormGroup>
- <FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink>
- <FormKeyValueView v-else>
- <template #key>{{ $ts.instanceInfo }}</template>
- <template #value>(Local user)</template>
- </FormKeyValueView>
- </FormGroup>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormObjectView from '@/components/debobigego/object-view.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
-import * as os from '@/os';
-import number from '@/filters/number';
-import bytes from '@/filters/bytes';
-import * as symbols from '@/symbols';
-import { url } from '@/config';
-
-export default defineComponent({
- components: {
- FormBase,
- FormTextarea,
- FormObjectView,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- FormSuspense,
- },
-
- props: {
- userId: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.userInfo,
- icon: 'fas fa-info-circle'
- },
- user: null,
- apPromiseFactory: null,
- }
- },
-
- mounted() {
- this.fetch();
- },
-
- methods: {
- number,
- bytes,
-
- async fetch() {
- this.user = await os.api('users/show', {
- userId: this.userId
- });
-
- this.apPromiseFactory = () => os.api('ap/get', {
- uri: this.user.uri || `${url}/users/${this.user.id}`
- });
- }
- }
-});
-</script>
diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue
index 0fd208a64a..4bdc82f601 100644
--- a/packages/client/src/pages/user-info.vue
+++ b/packages/client/src/pages/user-info.vue
@@ -1,70 +1,75 @@
<template>
-<FormBase>
+<MkSpacer :content-max="500" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
- <div class="_debobigegoItem aeakzknw">
- <MkAvatar class="avatar" :user="user" :show-indicator="true"/>
- </div>
-
- <FormLink :to="userPage(user)">Profile</FormLink>
+ <div class="_formRoot">
+ <div class="_formBlock aeakzknw">
+ <MkAvatar class="avatar" :user="user" :show-indicator="true"/>
+ </div>
- <FormGroup>
- <FormKeyValueView>
- <template #key>Acct</template>
- <template #value><span class="_monospace">{{ acct(user) }}</span></template>
- </FormKeyValueView>
+ <FormLink :to="userPage(user)">Profile</FormLink>
- <FormKeyValueView>
- <template #key>ID</template>
- <template #value><span class="_monospace">{{ user.id }}</span></template>
- </FormKeyValueView>
- </FormGroup>
+ <div class="_formBlock">
+ <MkKeyValue :copy="acct(user)" oneline style="margin: 1em 0;">
+ <template #key>Acct</template>
+ <template #value><span class="_monospace">{{ acct(user) }}</span></template>
+ </MkKeyValue>
- <FormGroup v-if="iAmModerator">
- <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch>
- <FormSwitch v-model="silenced" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch>
- <FormSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch>
- </FormGroup>
+ <MkKeyValue :copy="user.id" oneline style="margin: 1em 0;">
+ <template #key>ID</template>
+ <template #value><span class="_monospace">{{ user.id }}</span></template>
+ </MkKeyValue>
+ </div>
- <FormGroup>
- <FormButton v-if="user.host != null" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton>
- <FormButton v-if="user.host == null && iAmModerator" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
- </FormGroup>
+ <FormSection v-if="iAmModerator">
+ <template #label>Moderation</template>
+ <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch>
+ <FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch>
+ <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch>
+ <FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
+ </FormSection>
- <FormGroup>
- <FormLink :to="`/user-ap-info/${user.id}`">ActivityPub</FormLink>
+ <FormSection>
+ <template #label>ActivityPub</template>
- <FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink>
- <FormKeyValueView v-else>
- <template #key>{{ $ts.instanceInfo }}</template>
- <template #value>(Local user)</template>
- </FormKeyValueView>
- </FormGroup>
+ <div class="_formBlock">
+ <MkKeyValue v-if="user.host" oneline style="margin: 1em 0;">
+ <template #key>{{ $ts.instanceInfo }}</template>
+ <template #value><MkA :to="`/instance-info/${user.host}`" class="_link">{{ user.host }} <i class="fas fa-angle-right"></i></MkA></template>
+ </MkKeyValue>
+ <MkKeyValue v-else oneline style="margin: 1em 0;">
+ <template #key>{{ $ts.instanceInfo }}</template>
+ <template #value>(Local user)</template>
+ </MkKeyValue>
+ <MkKeyValue oneline style="margin: 1em 0;">
+ <template #key>{{ $ts.updatedAt }}</template>
+ <template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
+ </MkKeyValue>
+ <MkKeyValue v-if="ap" oneline style="margin: 1em 0;">
+ <template #key>Type</template>
+ <template #value><span class="_monospace">{{ ap.type }}</span></template>
+ </MkKeyValue>
+ </div>
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts.updatedAt }}</template>
- <template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
- </FormKeyValueView>
- </FormGroup>
+ <FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton>
+ </FormSection>
- <FormObjectView tall :value="user">
- <span>Raw</span>
- </FormObjectView>
+ <MkObjectView tall :value="user">
+ </MkObjectView>
+ </div>
</FormSuspense>
-</FormBase>
+</MkSpacer>
</template>
<script lang="ts">
import { computed, defineAsyncComponent, defineComponent } from 'vue';
-import FormObjectView from '@/components/debobigego/object-view.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormLink from '@/components/debobigego/link.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import MkObjectView from '@/components/object-view.vue';
+import FormTextarea from '@/components/form/textarea.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormLink from '@/components/form/link.vue';
+import FormSection from '@/components/form/section.vue';
+import FormButton from '@/components/ui/button.vue';
+import MkKeyValue from '@/components/key-value.vue';
+import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import number from '@/filters/number';
import bytes from '@/filters/bytes';
@@ -74,14 +79,13 @@ import { userPage, acct } from '@/filters/user';
export default defineComponent({
components: {
- FormBase,
+ FormSection,
FormTextarea,
FormSwitch,
- FormObjectView,
+ MkObjectView,
FormButton,
FormLink,
- FormGroup,
- FormKeyValueView,
+ MkKeyValue,
FormSuspense,
},
@@ -97,6 +101,7 @@ export default defineComponent({
[symbols.PAGE_INFO]: computed(() => ({
title: this.user ? acct(this.user) : this.$ts.userInfo,
icon: 'fas fa-info-circle',
+ bg: 'var(--bg)',
actions: this.user ? [this.user.url ? {
text: this.user.url,
icon: 'fas fa-external-link-alt',
@@ -108,6 +113,7 @@ export default defineComponent({
init: null,
user: null,
info: null,
+ ap: null,
moderator: false,
silenced: false,
suspended: false,
@@ -126,6 +132,13 @@ export default defineComponent({
this.init = this.createFetcher();
},
immediate: true
+ },
+ user() {
+ os.api('ap/get', {
+ uri: this.user.uri || `${url}/users/${this.user.id}`
+ }).then(res => {
+ this.ap = res;
+ });
}
},
@@ -234,7 +247,6 @@ export default defineComponent({
.aeakzknw {
> .avatar {
display: block;
- margin: 0 auto;
width: 64px;
height: 64px;
}
diff --git a/packages/client/src/pages/user/clips.vue b/packages/client/src/pages/user/clips.vue
index aad5317ce0..870e6f7174 100644
--- a/packages/client/src/pages/user/clips.vue
+++ b/packages/client/src/pages/user/clips.vue
@@ -28,7 +28,7 @@ export default defineComponent({
data() {
return {
pagination: {
- endpoint: 'users/clips',
+ endpoint: 'users/clips' as const,
limit: 20,
params: {
userId: this.user.id,
diff --git a/packages/client/src/pages/user/follow-list.vue b/packages/client/src/pages/user/follow-list.vue
index 9fb8943fb8..98a1fc0f86 100644
--- a/packages/client/src/pages/user/follow-list.vue
+++ b/packages/client/src/pages/user/follow-list.vue
@@ -1,6 +1,6 @@
<template>
<div>
- <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="mk-following-or-followers">
+ <MkPagination v-slot="{items}" ref="list" :pagination="type === 'following' ? followingPagination : followersPagination" class="mk-following-or-followers">
<div class="users _isolated">
<MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" class="user" :user="user"/>
</div>
@@ -8,50 +8,32 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as misskey from 'misskey-js';
import MkUserInfo from '@/components/user-info.vue';
import MkPagination from '@/components/ui/pagination.vue';
-export default defineComponent({
- components: {
- MkPagination,
- MkUserInfo,
- },
+const props = defineProps<{
+ user: misskey.entities.User;
+ type: 'following' | 'followers';
+}>();
- props: {
- user: {
- type: Object,
- required: true
- },
- type: {
- type: String,
- required: true
- },
- },
+const followingPagination = {
+ endpoint: 'users/following' as const,
+ limit: 20,
+ params: computed(() => ({
+ userId: props.user.id,
+ })),
+};
- data() {
- return {
- pagination: {
- endpoint: () => this.type === 'following' ? 'users/following' : 'users/followers',
- limit: 20,
- params: {
- userId: this.user.id,
- }
- },
- };
- },
-
- watch: {
- type() {
- this.$refs.list.reload();
- },
-
- user() {
- this.$refs.list.reload();
- }
- }
-});
+const followersPagination = {
+ endpoint: 'users/followers' as const,
+ limit: 20,
+ params: computed(() => ({
+ userId: props.user.id,
+ })),
+};
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/pages/user/gallery.vue b/packages/client/src/pages/user/gallery.vue
index 860aa9f44f..07dda4a292 100644
--- a/packages/client/src/pages/user/gallery.vue
+++ b/packages/client/src/pages/user/gallery.vue
@@ -9,7 +9,7 @@
</template>
<script lang="ts">
-import { defineComponent } from 'vue';
+import { computed, defineComponent } from 'vue';
import MkGalleryPostPreview from '@/components/gallery-post-preview.vue';
import MkPagination from '@/components/ui/pagination.vue';
@@ -29,20 +29,14 @@ export default defineComponent({
data() {
return {
pagination: {
- endpoint: 'users/gallery/posts',
+ endpoint: 'users/gallery/posts' as const,
limit: 6,
- params: () => ({
+ params: computed(() => ({
userId: this.user.id
- })
+ })),
},
};
},
-
- watch: {
- user() {
- this.$refs.list.reload();
- }
- }
});
</script>
diff --git a/packages/client/src/pages/user/index.activity.vue b/packages/client/src/pages/user/index.activity.vue
index e51d6c6090..43a4f476f1 100644
--- a/packages/client/src/pages/user/index.activity.vue
+++ b/packages/client/src/pages/user/index.activity.vue
@@ -8,27 +8,16 @@
</MkContainer>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@/os';
+<script lang="ts" setup>
+import { } from 'vue';
+import * as misskey from 'misskey-js';
import MkContainer from '@/components/ui/container.vue';
import MkChart from '@/components/chart.vue';
-export default defineComponent({
- components: {
- MkContainer,
- MkChart,
- },
- props: {
- user: {
- type: Object,
- required: true
- },
- limit: {
- type: Number,
- required: false,
- default: 40
- }
- },
+const props = withDefaults(defineProps<{
+ user: misskey.entities.User;
+ limit?: number;
+}>(), {
+ limit: 40,
});
</script>
diff --git a/packages/client/src/pages/user/index.timeline.vue b/packages/client/src/pages/user/index.timeline.vue
index 2ffa496979..a1329a7411 100644
--- a/packages/client/src/pages/user/index.timeline.vue
+++ b/packages/client/src/pages/user/index.timeline.vue
@@ -1,60 +1,36 @@
<template>
<div v-sticky-container class="yrzkoczt">
- <MkTab v-model="with_" class="tab">
+ <MkTab v-model="include" class="tab">
<option :value="null">{{ $ts.notes }}</option>
<option value="replies">{{ $ts.notesAndReplies }}</option>
<option value="files">{{ $ts.withFiles }}</option>
</MkTab>
- <XNotes ref="timeline" :no-gap="true" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/>
+ <XNotes :no-gap="true" :pagination="pagination"/>
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref, computed } from 'vue';
+import * as misskey from 'misskey-js';
import XNotes from '@/components/notes.vue';
import MkTab from '@/components/tab.vue';
import * as os from '@/os';
-export default defineComponent({
- components: {
- XNotes,
- MkTab,
- },
+const props = defineProps<{
+ user: misskey.entities.UserDetailed;
+}>();
- props: {
- user: {
- type: Object,
- required: true,
- },
- },
+const include = ref<string | null>(null);
- data() {
- return {
- date: null,
- with_: null,
- pagination: {
- endpoint: 'users/notes',
- limit: 10,
- params: init => ({
- userId: this.user.id,
- includeReplies: this.with_ === 'replies',
- withFiles: this.with_ === 'files',
- untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
- })
- }
- };
- },
-
- watch: {
- user() {
- this.$refs.timeline.reload();
- },
-
- with_() {
- this.$refs.timeline.reload();
- },
- },
-});
+const pagination = {
+ endpoint: 'users/notes' as const,
+ limit: 10,
+ params: computed(() => ({
+ userId: props.user.id,
+ includeReplies: include.value === 'replies',
+ withFiles: include.value === 'files',
+ })),
+};
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue
index 0b96368587..599e24d81c 100644
--- a/packages/client/src/pages/user/index.vue
+++ b/packages/client/src/pages/user/index.vue
@@ -1,196 +1,125 @@
<template>
<div>
-<transition name="fade" mode="out-in">
- <div v-if="user && narrow === false" class="ftskorzw wide">
- <MkRemoteCaution v-if="user.host != null" :href="user.url"/>
+ <transition name="fade" mode="out-in">
+ <MkSpacer v-if="user" :content-max="narrow ? 800 : 1100">
+ <div v-size="{ max: [500] }" class="ftskorzw" :class="{ wide: !narrow }">
+ <div class="main">
+ <!-- TODO -->
+ <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> -->
+ <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> -->
- <div class="banner-container" :style="style">
- <div ref="banner" class="banner" :style="style"></div>
- </div>
- <div class="contents">
- <div class="side _forceContainerFull_">
- <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
- <div class="name">
- <MkUserName :user="user" :nowrap="false" class="name"/>
- <MkAcct :user="user" :detail="true" class="acct"/>
- </div>
- <div v-if="$i && $i.id != user.id && user.isFollowed" class="followed"><span>{{ $ts.followsYou }}</span></div>
- <div class="status">
- <MkA :to="userPage(user)" :class="{ active: page === 'index' }">
- <b>{{ number(user.notesCount) }}</b>
- <span>{{ $ts.notes }}</span>
- </MkA>
- <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
- <b>{{ number(user.followingCount) }}</b>
- <span>{{ $ts.following }}</span>
- </MkA>
- <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
- <b>{{ number(user.followersCount) }}</b>
- <span>{{ $ts.followers }}</span>
- </MkA>
- </div>
- <div class="description">
- <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/>
- <p v-else class="empty">{{ $ts.noAccountDescription }}</p>
- </div>
- <div class="fields system">
- <dl v-if="user.location" class="field">
- <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt>
- <dd class="value">{{ user.location }}</dd>
- </dl>
- <dl v-if="user.birthday" class="field">
- <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt>
- <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
- </dl>
- <dl class="field">
- <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt>
- <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd>
- </dl>
- </div>
- <div v-if="user.fields.length > 0" class="fields">
- <dl v-for="(field, i) in user.fields" :key="i" class="field">
- <dt class="name">
- <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
- </dt>
- <dd class="value">
- <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/>
- </dd>
- </dl>
- </div>
- <XActivity :key="user.id" :user="user" class="_gap"/>
- <XPhotos :key="user.id" :user="user" class="_gap"/>
- </div>
- <div class="main">
- <div class="actions">
- <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button>
- <MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
- </div>
- <template v-if="page === 'index'">
- <div v-if="user.pinnedNotes.length > 0" class="_gap">
- <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _gap" :note="note" :pinned="true" @update:note="pinnedNoteUpdated(note, $event)"/>
- </div>
- <div class="_gap">
- <XUserTimeline :user="user"/>
- </div>
- </template>
- <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_gap"/>
- <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_gap"/>
- <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/>
- <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/>
- </div>
- </div>
- </div>
- <MkSpacer v-else-if="user && narrow === true" :content-max="800">
- <div v-size="{ max: [500] }" class="ftskorzw narrow">
- <!-- TODO -->
- <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> -->
- <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> -->
+ <div class="profile">
+ <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/>
- <div class="profile">
- <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/>
-
- <div :key="user.id" class="_block main">
- <div class="banner-container" :style="style">
- <div ref="banner" class="banner" :style="style"></div>
- <div class="fade"></div>
- <div class="title">
- <MkUserName class="name" :user="user" :nowrap="true"/>
- <div class="bottom">
- <span class="username"><MkAcct :user="user" :detail="true" /></span>
- <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span>
- <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span>
- <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span>
- <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span>
+ <div :key="user.id" class="_block main">
+ <div class="banner-container" :style="style">
+ <div ref="banner" class="banner" :style="style"></div>
+ <div class="fade"></div>
+ <div class="title">
+ <MkUserName class="name" :user="user" :nowrap="true"/>
+ <div class="bottom">
+ <span class="username"><MkAcct :user="user" :detail="true" /></span>
+ <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span>
+ <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span>
+ <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span>
+ <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span>
+ </div>
+ </div>
+ <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span>
+ <div v-if="$i" class="actions">
+ <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button>
+ <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
+ </div>
+ </div>
+ <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
+ <div class="title">
+ <MkUserName :user="user" :nowrap="false" class="name"/>
+ <div class="bottom">
+ <span class="username"><MkAcct :user="user" :detail="true" /></span>
+ <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span>
+ <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span>
+ <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span>
+ <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span>
+ </div>
+ </div>
+ <div class="description">
+ <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/>
+ <p v-else class="empty">{{ $ts.noAccountDescription }}</p>
+ </div>
+ <div class="fields system">
+ <dl v-if="user.location" class="field">
+ <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt>
+ <dd class="value">{{ user.location }}</dd>
+ </dl>
+ <dl v-if="user.birthday" class="field">
+ <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt>
+ <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
+ </dl>
+ <dl class="field">
+ <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt>
+ <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd>
+ </dl>
+ </div>
+ <div v-if="user.fields.length > 0" class="fields">
+ <dl v-for="(field, i) in user.fields" :key="i" class="field">
+ <dt class="name">
+ <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
+ </dt>
+ <dd class="value">
+ <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/>
+ </dd>
+ </dl>
+ </div>
+ <div class="status">
+ <MkA v-click-anime :to="userPage(user)" :class="{ active: page === 'index' }">
+ <b>{{ number(user.notesCount) }}</b>
+ <span>{{ $ts.notes }}</span>
+ </MkA>
+ <MkA v-click-anime :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
+ <b>{{ number(user.followingCount) }}</b>
+ <span>{{ $ts.following }}</span>
+ </MkA>
+ <MkA v-click-anime :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
+ <b>{{ number(user.followersCount) }}</b>
+ <span>{{ $ts.followers }}</span>
+ </MkA>
</div>
- </div>
- <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span>
- <div v-if="$i" class="actions">
- <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button>
- <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
- </div>
- </div>
- <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
- <div class="title">
- <MkUserName :user="user" :nowrap="false" class="name"/>
- <div class="bottom">
- <span class="username"><MkAcct :user="user" :detail="true" /></span>
- <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span>
- <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span>
- <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span>
- <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span>
</div>
</div>
- <div class="description">
- <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/>
- <p v-else class="empty">{{ $ts.noAccountDescription }}</p>
- </div>
- <div class="fields system">
- <dl v-if="user.location" class="field">
- <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt>
- <dd class="value">{{ user.location }}</dd>
- </dl>
- <dl v-if="user.birthday" class="field">
- <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt>
- <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
- </dl>
- <dl class="field">
- <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt>
- <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd>
- </dl>
- </div>
- <div v-if="user.fields.length > 0" class="fields">
- <dl v-for="(field, i) in user.fields" :key="i" class="field">
- <dt class="name">
- <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
- </dt>
- <dd class="value">
- <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/>
- </dd>
- </dl>
- </div>
- <div class="status">
- <MkA v-click-anime :to="userPage(user)" :class="{ active: page === 'index' }">
- <b>{{ number(user.notesCount) }}</b>
- <span>{{ $ts.notes }}</span>
- </MkA>
- <MkA v-click-anime :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
- <b>{{ number(user.followingCount) }}</b>
- <span>{{ $ts.following }}</span>
- </MkA>
- <MkA v-click-anime :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
- <b>{{ number(user.followersCount) }}</b>
- <span>{{ $ts.followers }}</span>
- </MkA>
- </div>
- </div>
- </div>
- <div class="contents">
- <template v-if="page === 'index'">
- <div>
- <div v-if="user.pinnedNotes.length > 0" class="_gap">
- <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _block" :note="note" :pinned="true" @update:note="pinnedNoteUpdated(note, $event)"/>
- </div>
- <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo>
- <XPhotos :key="user.id" :user="user"/>
- <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/>
- </div>
- <div>
- <XUserTimeline :user="user"/>
+ <div class="contents">
+ <template v-if="page === 'index'">
+ <div>
+ <div v-if="user.pinnedNotes.length > 0" class="_gap">
+ <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _block" :note="note" :pinned="true"/>
+ </div>
+ <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo>
+ <template v-if="narrow">
+ <XPhotos :key="user.id" :user="user"/>
+ <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/>
+ </template>
+ </div>
+ <div>
+ <XUserTimeline :user="user"/>
+ </div>
+ </template>
+ <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/>
+ <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/>
+ <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/>
+ <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/>
+ <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/>
+ <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/>
</div>
- </template>
- <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/>
- <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/>
- <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/>
- <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/>
- <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/>
- <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/>
+ </div>
+ <div v-if="!narrow" class="sub">
+ <XPhotos :key="user.id" :user="user"/>
+ <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/>
+ </div>
</div>
- </div>
- </MkSpacer>
- <MkError v-else-if="error" @retry="fetch()"/>
- <MkLoading v-else/>
-</transition>
+ </MkSpacer>
+ <MkError v-else-if="error" @retry="fetch()"/>
+ <MkLoading v-else/>
+ </transition>
</div>
</template>
@@ -314,7 +243,7 @@ export default defineComponent({
mounted() {
window.requestAnimationFrame(this.parallaxLoop);
- this.narrow = true//this.$el.clientWidth < 1000;
+ this.narrow = this.$el.clientWidth < 1000;
},
beforeUnmount() {
@@ -356,11 +285,6 @@ export default defineComponent({
banner.style.backgroundPosition = `center calc(50% - ${pos}px)`;
},
- pinnedNoteUpdated(oldValue, newValue) {
- const i = this.user.pinnedNotes.findIndex(n => n === oldValue);
- this.user.pinnedNotes[i] = newValue;
- },
-
number,
userPage
@@ -378,447 +302,289 @@ export default defineComponent({
opacity: 0;
}
-.ftskorzw.wide {
+.ftskorzw {
- > .banner-container {
- position: relative;
- height: 300px;
- overflow: hidden;
- background-size: cover;
- background-position: center;
+ > .main {
- > .banner {
- height: 100%;
- background-color: #4c5e6d;
- background-size: cover;
- background-position: center;
- box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset;
- will-change: background-position;
+ > .punished {
+ font-size: 0.8em;
+ padding: 16px;
}
- }
-
- > .contents {
- display: flex;
- padding: 16px;
-
- > .side {
- width: 360px;
- > .avatar {
- display: block;
- width: 180px;
- height: 180px;
- margin: -130px auto 0 auto;
- }
-
- > .name {
- padding: 16px 0px 20px 0;
- text-align: center;
-
- > .name {
- display: block;
- font-size: 1.75em;
- font-weight: bold;
- }
- }
-
- > .followed {
- text-align: center;
-
- > span {
- display: inline-block;
- font-size: 80%;
- padding: 8px 12px;
- margin-bottom: 20px;
- border: solid 0.5px var(--divider);
- border-radius: 999px;
- }
- }
+ > .profile {
- > .status {
- display: flex;
- padding: 20px 16px;
- border-top: solid 0.5px var(--divider);
- font-size: 90%;
+ > .main {
+ position: relative;
+ overflow: hidden;
- > a {
- flex: 1;
- text-align: center;
+ > .banner-container {
+ position: relative;
+ height: 250px;
+ overflow: hidden;
+ background-size: cover;
+ background-position: center;
- &.active {
- color: var(--accent);
+ > .banner {
+ height: 100%;
+ background-color: #4c5e6d;
+ background-size: cover;
+ background-position: center;
+ box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset;
+ will-change: background-position;
}
- &:hover {
- text-decoration: none;
+ > .fade {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 78px;
+ background: linear-gradient(transparent, rgba(#000, 0.7));
}
- > b {
- display: block;
- line-height: 16px;
+ > .followed {
+ position: absolute;
+ top: 12px;
+ left: 12px;
+ padding: 4px 8px;
+ color: #fff;
+ background: rgba(0, 0, 0, 0.7);
+ font-size: 0.7em;
+ border-radius: 6px;
}
- > span {
- font-size: 75%;
- }
- }
- }
+ > .actions {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ -webkit-backdrop-filter: var(--blur, blur(8px));
+ backdrop-filter: var(--blur, blur(8px));
+ background: rgba(0, 0, 0, 0.2);
+ padding: 8px;
+ border-radius: 24px;
- > .description {
- padding: 20px 16px;
- border-top: solid 0.5px var(--divider);
- font-size: 90%;
- }
+ > .menu {
+ vertical-align: bottom;
+ height: 31px;
+ width: 31px;
+ color: #fff;
+ text-shadow: 0 0 8px #000;
+ font-size: 16px;
+ }
- > .fields {
- padding: 20px 16px;
- border-top: solid 0.5px var(--divider);
- font-size: 90%;
+ > .koudoku {
+ margin-left: 4px;
+ vertical-align: bottom;
+ }
+ }
- > .field {
- display: flex;
- padding: 0;
- margin: 0;
- align-items: center;
+ > .title {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ padding: 0 0 8px 154px;
+ box-sizing: border-box;
+ color: #fff;
- &:not(:last-child) {
- margin-bottom: 8px;
- }
+ > .name {
+ display: block;
+ margin: 0;
+ line-height: 32px;
+ font-weight: bold;
+ font-size: 1.8em;
+ text-shadow: 0 0 8px #000;
+ }
- > .name {
- width: 30%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- font-weight: bold;
- }
+ > .bottom {
+ > * {
+ display: inline-block;
+ margin-right: 16px;
+ line-height: 20px;
+ opacity: 0.8;
- > .value {
- width: 70%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- margin: 0;
+ &.username {
+ font-weight: bold;
+ }
+ }
+ }
}
}
- }
- }
- > .main {
- flex: 1;
- margin-left: var(--margin);
- min-width: 0;
-
- > .nav {
- display: flex;
- align-items: center;
- margin-top: var(--margin);
- //font-size: 120%;
- font-weight: bold;
-
- > .link {
- display: inline-block;
- padding: 15px 24px 12px 24px;
+ > .title {
+ display: none;
text-align: center;
- border-bottom: solid 3px transparent;
-
- &:hover {
- text-decoration: none;
- }
-
- &.active {
- color: var(--accent);
- border-bottom-color: var(--accent);
- }
+ padding: 50px 8px 16px 8px;
+ font-weight: bold;
+ border-bottom: solid 0.5px var(--divider);
- &:not(.active):hover {
- color: var(--fgHighlighted);
+ > .bottom {
+ > * {
+ display: inline-block;
+ margin-right: 8px;
+ opacity: 0.8;
+ }
}
+ }
- > .icon {
- margin-right: 6px;
- }
+ > .avatar {
+ display: block;
+ position: absolute;
+ top: 170px;
+ left: 16px;
+ z-index: 2;
+ width: 120px;
+ height: 120px;
+ box-shadow: 1px 1px 3px rgba(#000, 0.2);
}
- > .actions {
- display: flex;
- align-items: center;
- margin-left: auto;
+ > .description {
+ padding: 24px 24px 24px 154px;
+ font-size: 0.95em;
- > .menu {
- padding: 12px 16px;
+ > .empty {
+ margin: 0;
+ opacity: 0.5;
}
}
- }
- }
- }
-}
-
-.ftskorzw.narrow {
- box-sizing: border-box;
- overflow: clip;
- background: var(--bg);
-
- > .punished {
- font-size: 0.8em;
- padding: 16px;
- }
-
- > .profile {
-
- > .main {
- position: relative;
- overflow: hidden;
- > .banner-container {
- position: relative;
- height: 250px;
- overflow: hidden;
- background-size: cover;
- background-position: center;
-
- > .banner {
- height: 100%;
- background-color: #4c5e6d;
- background-size: cover;
- background-position: center;
- box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset;
- will-change: background-position;
- }
+ > .fields {
+ padding: 24px;
+ font-size: 0.9em;
+ border-top: solid 0.5px var(--divider);
- > .fade {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 78px;
- background: linear-gradient(transparent, rgba(#000, 0.7));
- }
+ > .field {
+ display: flex;
+ padding: 0;
+ margin: 0;
+ align-items: center;
- > .followed {
- position: absolute;
- top: 12px;
- left: 12px;
- padding: 4px 8px;
- color: #fff;
- background: rgba(0, 0, 0, 0.7);
- font-size: 0.7em;
- border-radius: 6px;
- }
+ &:not(:last-child) {
+ margin-bottom: 8px;
+ }
- > .actions {
- position: absolute;
- top: 12px;
- right: 12px;
- -webkit-backdrop-filter: var(--blur, blur(8px));
- backdrop-filter: var(--blur, blur(8px));
- background: rgba(0, 0, 0, 0.2);
- padding: 8px;
- border-radius: 24px;
+ > .name {
+ width: 30%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ font-weight: bold;
+ text-align: center;
+ }
- > .menu {
- vertical-align: bottom;
- height: 31px;
- width: 31px;
- color: #fff;
- text-shadow: 0 0 8px #000;
- font-size: 16px;
+ > .value {
+ width: 70%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ margin: 0;
+ }
}
- > .koudoku {
- margin-left: 4px;
- vertical-align: bottom;
+ &.system > .field > .name {
}
}
- > .title {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- padding: 0 0 8px 154px;
- box-sizing: border-box;
- color: #fff;
+ > .status {
+ display: flex;
+ padding: 24px;
+ border-top: solid 0.5px var(--divider);
- > .name {
- display: block;
- margin: 0;
- line-height: 32px;
- font-weight: bold;
- font-size: 1.8em;
- text-shadow: 0 0 8px #000;
- }
+ > a {
+ flex: 1;
+ text-align: center;
- > .bottom {
- > * {
- display: inline-block;
- margin-right: 16px;
- line-height: 20px;
- opacity: 0.8;
+ &.active {
+ color: var(--accent);
+ }
- &.username {
- font-weight: bold;
- }
+ &:hover {
+ text-decoration: none;
}
- }
- }
- }
- > .title {
- display: none;
- text-align: center;
- padding: 50px 8px 16px 8px;
- font-weight: bold;
- border-bottom: solid 0.5px var(--divider);
+ > b {
+ display: block;
+ line-height: 16px;
+ }
- > .bottom {
- > * {
- display: inline-block;
- margin-right: 8px;
- opacity: 0.8;
+ > span {
+ font-size: 70%;
+ }
}
}
}
+ }
- > .avatar {
- display: block;
- position: absolute;
- top: 170px;
- left: 16px;
- z-index: 2;
- width: 120px;
- height: 120px;
- box-shadow: 1px 1px 3px rgba(#000, 0.2);
- }
-
- > .description {
- padding: 24px 24px 24px 154px;
- font-size: 0.95em;
-
- > .empty {
- margin: 0;
- opacity: 0.5;
- }
+ > .contents {
+ > .content {
+ margin-bottom: var(--margin);
}
+ }
+ }
- > .fields {
- padding: 24px;
- font-size: 0.9em;
- border-top: solid 0.5px var(--divider);
-
- > .field {
- display: flex;
- padding: 0;
- margin: 0;
- align-items: center;
+ &.max-width_500px {
+ > .main {
+ > .profile > .main {
+ > .banner-container {
+ height: 140px;
- &:not(:last-child) {
- margin-bottom: 8px;
+ > .fade {
+ display: none;
}
- > .name {
- width: 30%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- font-weight: bold;
- text-align: center;
- }
-
- > .value {
- width: 70%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- margin: 0;
+ > .title {
+ display: none;
}
}
- &.system > .field > .name {
+ > .title {
+ display: block;
}
- }
- > .status {
- display: flex;
- padding: 24px;
- border-top: solid 0.5px var(--divider);
+ > .avatar {
+ top: 90px;
+ left: 0;
+ right: 0;
+ width: 92px;
+ height: 92px;
+ margin: auto;
+ }
- > a {
- flex: 1;
+ > .description {
+ padding: 16px;
text-align: center;
-
- &.active {
- color: var(--accent);
- }
-
- &:hover {
- text-decoration: none;
- }
-
- > b {
- display: block;
- line-height: 16px;
- }
-
- > span {
- font-size: 70%;
- }
}
- }
- }
- }
- > .contents {
- > .content {
- margin-bottom: var(--margin);
- }
- }
-
- &.max-width_500px {
- > .profile > .main {
- > .banner-container {
- height: 140px;
-
- > .fade {
- display: none;
+ > .fields {
+ padding: 16px;
}
- > .title {
- display: none;
+ > .status {
+ padding: 16px;
}
}
- > .title {
- display: block;
- }
-
- > .avatar {
- top: 90px;
- left: 0;
- right: 0;
- width: 92px;
- height: 92px;
- margin: auto;
- }
-
- > .description {
- padding: 16px;
- text-align: center;
+ > .contents {
+ > .nav {
+ font-size: 80%;
+ }
}
+ }
+ }
- > .fields {
- padding: 16px;
- }
+ &.wide {
+ display: flex;
+ width: 100%;
- > .status {
- padding: 16px;
- }
+ > .main {
+ width: 100%;
+ min-width: 0;
}
- > .contents {
- > .nav {
- font-size: 80%;
- }
+ > .sub {
+ max-width: 350px;
+ min-width: 350px;
+ margin-left: var(--margin);
}
}
}
diff --git a/packages/client/src/pages/user/pages.vue b/packages/client/src/pages/user/pages.vue
index 40d1fe3842..ad101158e0 100644
--- a/packages/client/src/pages/user/pages.vue
+++ b/packages/client/src/pages/user/pages.vue
@@ -6,42 +6,23 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as misskey from 'misskey-js';
import MkPagePreview from '@/components/page-preview.vue';
import MkPagination from '@/components/ui/pagination.vue';
-export default defineComponent({
- components: {
- MkPagination,
- MkPagePreview,
- },
+const props = defineProps<{
+ user: misskey.entities.User;
+}>();
- props: {
- user: {
- type: Object,
- required: true
- },
- },
-
- data() {
- return {
- pagination: {
- endpoint: 'users/pages',
- limit: 20,
- params: {
- userId: this.user.id,
- }
- },
- };
- },
-
- watch: {
- user() {
- this.$refs.list.reload();
- }
- }
-});
+const pagination = {
+ endpoint: 'users/pages' as const,
+ limit: 20,
+ params: computed(() => ({
+ userId: props.user.id,
+ })),
+};
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/pages/user/reactions.vue b/packages/client/src/pages/user/reactions.vue
index 69c27de55b..d2c1f92ebb 100644
--- a/packages/client/src/pages/user/reactions.vue
+++ b/packages/client/src/pages/user/reactions.vue
@@ -7,50 +7,30 @@
<MkReactionIcon class="reaction" :reaction="item.type" :custom-emojis="item.note.emojis" :no-style="true"/>
<MkTime :time="item.createdAt" class="createdAt"/>
</div>
- <MkNote :key="item.id" :note="item.note" @update:note="updated(note, $event)"/>
+ <MkNote :key="item.id" :note="item.note"/>
</div>
</MkPagination>
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as misskey from 'misskey-js';
import MkPagination from '@/components/ui/pagination.vue';
import MkNote from '@/components/note.vue';
import MkReactionIcon from '@/components/reaction-icon.vue';
-export default defineComponent({
- components: {
- MkPagination,
- MkNote,
- MkReactionIcon,
- },
+const props = defineProps<{
+ user: misskey.entities.User;
+}>();
- props: {
- user: {
- type: Object,
- required: true
- },
- },
-
- data() {
- return {
- pagination: {
- endpoint: 'users/reactions',
- limit: 20,
- params: {
- userId: this.user.id,
- }
- },
- };
- },
-
- watch: {
- user() {
- this.$refs.list.reload();
- }
- },
-});
+const pagination = {
+ endpoint: 'users/reactions' as const,
+ limit: 20,
+ params: computed(() => ({
+ userId: props.user.id,
+ })),
+};
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/pages/v.vue b/packages/client/src/pages/v.vue
deleted file mode 100644
index 3b1bb20861..0000000000
--- a/packages/client/src/pages/v.vue
+++ /dev/null
@@ -1,29 +0,0 @@
-<template>
-<div>
- <section class="_section">
- <div class="_content" style="text-align: center;">
- <img src="/static-assets/icons/512.png" alt="" style="display: block; width: 100px; margin: 0 auto; border-radius: 16px;"/>
- <div style="margin-top: 0.75em;">Misskey</div>
- <div style="opacity: 0.5;">v{{ version }}</div>
- </div>
- </section>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { version } from '@/config';
-import * as symbols from '@/symbols';
-
-export default defineComponent({
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'Misskey',
- icon: null
- },
- version,
- }
- },
-});
-</script>