summaryrefslogtreecommitdiff
path: root/src/client/pages
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-11-13 12:23:49 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2021-11-13 12:23:49 +0900
commit2795fe457909c687f668d020ef65d52abc3182fb (patch)
tree0a52e4e4d854333496fcc487560c93c3de5d5eb5 /src/client/pages
parentMerge branch 'develop' (diff)
parent12.96.0 (diff)
downloadmisskey-2795fe457909c687f668d020ef65d52abc3182fb.tar.gz
misskey-2795fe457909c687f668d020ef65d52abc3182fb.tar.bz2
misskey-2795fe457909c687f668d020ef65d52abc3182fb.zip
Merge branch 'develop'
Diffstat (limited to 'src/client/pages')
-rw-r--r--src/client/pages/_error_.vue94
-rw-r--r--src/client/pages/_loading_.vue10
-rw-r--r--src/client/pages/about-misskey.vue238
-rw-r--r--src/client/pages/about.vue123
-rw-r--r--src/client/pages/admin/abuses.vue170
-rw-r--r--src/client/pages/admin/ads.vue138
-rw-r--r--src/client/pages/admin/announcements.vue125
-rw-r--r--src/client/pages/admin/bot-protection.vue138
-rw-r--r--src/client/pages/admin/database.vue61
-rw-r--r--src/client/pages/admin/email-settings.vue128
-rw-r--r--src/client/pages/admin/emoji-edit-dialog.vue120
-rw-r--r--src/client/pages/admin/emojis.vue263
-rw-r--r--src/client/pages/admin/file-dialog.vue129
-rw-r--r--src/client/pages/admin/files-settings.vue93
-rw-r--r--src/client/pages/admin/files.vue209
-rw-r--r--src/client/pages/admin/index.vue388
-rw-r--r--src/client/pages/admin/instance-block.vue72
-rw-r--r--src/client/pages/admin/instance.vue321
-rw-r--r--src/client/pages/admin/integrations-discord.vue85
-rw-r--r--src/client/pages/admin/integrations-github.vue85
-rw-r--r--src/client/pages/admin/integrations-twitter.vue85
-rw-r--r--src/client/pages/admin/integrations.vue74
-rw-r--r--src/client/pages/admin/metrics.vue472
-rw-r--r--src/client/pages/admin/object-storage.vue155
-rw-r--r--src/client/pages/admin/other-settings.vue83
-rw-r--r--src/client/pages/admin/overview.vue236
-rw-r--r--src/client/pages/admin/proxy-account.vue87
-rw-r--r--src/client/pages/admin/queue.chart.vue102
-rw-r--r--src/client/pages/admin/queue.vue73
-rw-r--r--src/client/pages/admin/relays.vue99
-rw-r--r--src/client/pages/admin/security.vue83
-rw-r--r--src/client/pages/admin/service-worker.vue85
-rw-r--r--src/client/pages/admin/settings.vue151
-rw-r--r--src/client/pages/admin/users.vue254
-rw-r--r--src/client/pages/advanced-theme-editor.vue352
-rw-r--r--src/client/pages/announcements.vue74
-rw-r--r--src/client/pages/antenna-timeline.vue147
-rw-r--r--src/client/pages/api-console.vue93
-rw-r--r--src/client/pages/auth.form.vue60
-rwxr-xr-xsrc/client/pages/auth.vue95
-rw-r--r--src/client/pages/channel-editor.vue129
-rw-r--r--src/client/pages/channel.vue186
-rw-r--r--src/client/pages/channels.vue77
-rw-r--r--src/client/pages/clip.vue154
-rw-r--r--src/client/pages/doc.vue240
-rw-r--r--src/client/pages/docs.vue152
-rw-r--r--src/client/pages/drive.vue28
-rw-r--r--src/client/pages/emojis.category.vue135
-rw-r--r--src/client/pages/emojis.emoji.vue94
-rw-r--r--src/client/pages/emojis.vue36
-rw-r--r--src/client/pages/explore.vue261
-rw-r--r--src/client/pages/favorites.vue60
-rw-r--r--src/client/pages/featured.vue43
-rw-r--r--src/client/pages/federation.vue265
-rw-r--r--src/client/pages/follow-requests.vue153
-rw-r--r--src/client/pages/follow.vue65
-rw-r--r--src/client/pages/gallery/edit.vue168
-rw-r--r--src/client/pages/gallery/index.vue152
-rw-r--r--src/client/pages/gallery/post.vue282
-rw-r--r--src/client/pages/instance-info.vue238
-rw-r--r--src/client/pages/mentions.vue42
-rw-r--r--src/client/pages/messages.vue45
-rw-r--r--src/client/pages/messaging/index.vue307
-rw-r--r--src/client/pages/messaging/messaging-room.form.vue348
-rw-r--r--src/client/pages/messaging/messaging-room.message.vue350
-rw-r--r--src/client/pages/messaging/messaging-room.vue470
-rw-r--r--src/client/pages/mfm-cheat-sheet.vue365
-rw-r--r--src/client/pages/miauth.vue100
-rw-r--r--src/client/pages/my-antennas/create.vue51
-rw-r--r--src/client/pages/my-antennas/edit.vue56
-rw-r--r--src/client/pages/my-antennas/editor.vue190
-rw-r--r--src/client/pages/my-antennas/index.vue71
-rw-r--r--src/client/pages/my-clips/index.vue104
-rw-r--r--src/client/pages/my-groups/group.vue184
-rw-r--r--src/client/pages/my-groups/index.vue121
-rw-r--r--src/client/pages/my-lists/index.vue88
-rw-r--r--src/client/pages/my-lists/list.vue170
-rw-r--r--src/client/pages/not-found.vue25
-rw-r--r--src/client/pages/note.vue209
-rw-r--r--src/client/pages/notifications.vue88
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.button.vue84
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.canvas.vue50
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.counter.vue46
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.if.vue84
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.image.vue72
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.note.vue65
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.number-input.vue46
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.post.vue43
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.radio-button.vue50
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.section.vue96
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.switch.vue46
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.text-input.vue39
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.text.vue57
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.textarea-input.vue40
-rw-r--r--src/client/pages/page-editor/els/page-editor.el.textarea.vue57
-rw-r--r--src/client/pages/page-editor/page-editor.blocks.vue78
-rw-r--r--src/client/pages/page-editor/page-editor.container.vue159
-rw-r--r--src/client/pages/page-editor/page-editor.script-block.vue281
-rw-r--r--src/client/pages/page-editor/page-editor.vue561
-rw-r--r--src/client/pages/page.vue311
-rw-r--r--src/client/pages/pages.vue96
-rw-r--r--src/client/pages/preview.vue32
-rw-r--r--src/client/pages/reset-password.vue69
-rw-r--r--src/client/pages/reversi/game.board.vue528
-rw-r--r--src/client/pages/reversi/game.setting.vue390
-rw-r--r--src/client/pages/reversi/game.vue76
-rw-r--r--src/client/pages/reversi/index.vue279
-rw-r--r--src/client/pages/room/preview.vue107
-rw-r--r--src/client/pages/room/room.vue285
-rw-r--r--src/client/pages/scratchpad.vue149
-rw-r--r--src/client/pages/search.vue53
-rw-r--r--src/client/pages/settings/2fa.vue247
-rw-r--r--src/client/pages/settings/account-info.vue185
-rw-r--r--src/client/pages/settings/accounts.vue149
-rw-r--r--src/client/pages/settings/api.vue65
-rw-r--r--src/client/pages/settings/apps.vue113
-rw-r--r--src/client/pages/settings/custom-css.vue73
-rw-r--r--src/client/pages/settings/deck.vue107
-rw-r--r--src/client/pages/settings/delete-account.vue68
-rw-r--r--src/client/pages/settings/drive.vue147
-rw-r--r--src/client/pages/settings/email-address.vue70
-rw-r--r--src/client/pages/settings/email-notification.vue91
-rw-r--r--src/client/pages/settings/email.vue66
-rw-r--r--src/client/pages/settings/experimental-features.vue52
-rw-r--r--src/client/pages/settings/general.vue223
-rw-r--r--src/client/pages/settings/import-export.vue112
-rw-r--r--src/client/pages/settings/index.vue326
-rw-r--r--src/client/pages/settings/integration.vue141
-rw-r--r--src/client/pages/settings/menu.vue117
-rw-r--r--src/client/pages/settings/mute-block.vue85
-rw-r--r--src/client/pages/settings/notifications.vue77
-rw-r--r--src/client/pages/settings/other.vue97
-rw-r--r--src/client/pages/settings/plugin.install.vue147
-rw-r--r--src/client/pages/settings/plugin.manage.vue115
-rw-r--r--src/client/pages/settings/plugin.vue44
-rw-r--r--src/client/pages/settings/privacy.vue108
-rw-r--r--src/client/pages/settings/profile.vue281
-rw-r--r--src/client/pages/settings/reaction.vue152
-rw-r--r--src/client/pages/settings/registry.keys.vue114
-rw-r--r--src/client/pages/settings/registry.value.vue149
-rw-r--r--src/client/pages/settings/registry.vue90
-rw-r--r--src/client/pages/settings/security.vue158
-rw-r--r--src/client/pages/settings/sounds.vue155
-rw-r--r--src/client/pages/settings/theme.install.vue105
-rw-r--r--src/client/pages/settings/theme.manage.vue105
-rw-r--r--src/client/pages/settings/theme.vue424
-rw-r--r--src/client/pages/settings/update.vue95
-rw-r--r--src/client/pages/settings/word-mute.vue110
-rw-r--r--src/client/pages/share.vue184
-rw-r--r--src/client/pages/signup-complete.vue50
-rw-r--r--src/client/pages/tag.vue57
-rw-r--r--src/client/pages/test.vue259
-rw-r--r--src/client/pages/theme-editor.vue306
-rw-r--r--src/client/pages/timeline.tutorial.vue131
-rw-r--r--src/client/pages/timeline.vue225
-rw-r--r--src/client/pages/user-ap-info.vue124
-rw-r--r--src/client/pages/user-info.vue245
-rw-r--r--src/client/pages/user-list-timeline.vue147
-rw-r--r--src/client/pages/user/clips.vue50
-rw-r--r--src/client/pages/user/follow-list.vue65
-rw-r--r--src/client/pages/user/gallery.vue56
-rw-r--r--src/client/pages/user/index.activity.vue34
-rw-r--r--src/client/pages/user/index.photos.vue107
-rw-r--r--src/client/pages/user/index.timeline.vue68
-rw-r--r--src/client/pages/user/index.vue829
-rw-r--r--src/client/pages/user/pages.vue49
-rw-r--r--src/client/pages/user/reactions.vue81
-rw-r--r--src/client/pages/v.vue29
-rw-r--r--src/client/pages/welcome.entrance.a.vue320
-rw-r--r--src/client/pages/welcome.entrance.b.vue236
-rw-r--r--src/client/pages/welcome.entrance.c.vue305
-rw-r--r--src/client/pages/welcome.setup.vue102
-rw-r--r--src/client/pages/welcome.timeline.vue99
-rw-r--r--src/client/pages/welcome.vue38
174 files changed, 0 insertions, 25640 deletions
diff --git a/src/client/pages/_error_.vue b/src/client/pages/_error_.vue
deleted file mode 100644
index d1cefad8dd..0000000000
--- a/src/client/pages/_error_.vue
+++ /dev/null
@@ -1,94 +0,0 @@
-<template>
-<MkLoading v-if="!loaded" />
-<transition :name="$store.state.animation ? 'zoom' : ''" appear>
- <div class="mjndxjch" v-show="loaded">
- <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>
- <template v-else>
- <p>{{ $ts.newVersionOfClientAvailable }}</p>
- <p>{{ $ts.youShouldUpgradeClient }}</p>
- <MkButton @click="reload" class="button primary">{{ $ts.reload }}</MkButton>
- </template>
- <p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p>
- <p v-if="error" class="error">ERROR: {{ error }}</p>
- </div>
-</transition>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as symbols from '@client/symbols';
-import { version } from '@client/config';
-import * as os from '@client/os';
-import { unisonReload } from '@client/scripts/unison-reload';
-
-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();
- },
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.mjndxjch {
- padding: 32px;
- text-align: center;
-
- > p {
- margin: 0 0 12px 0;
- }
-
- > .button {
- margin: 8px auto;
- }
-
- > img {
- vertical-align: bottom;
- height: 128px;
- margin-bottom: 24px;
- border-radius: 16px;
- }
-
- > .error {
- opacity: 0.7;
- }
-}
-</style>
diff --git a/src/client/pages/_loading_.vue b/src/client/pages/_loading_.vue
deleted file mode 100644
index 34ecaf9b33..0000000000
--- a/src/client/pages/_loading_.vue
+++ /dev/null
@@ -1,10 +0,0 @@
-<template>
-<MkLoading/>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@client/os';
-
-export default defineComponent({});
-</script>
diff --git a/src/client/pages/about-misskey.vue b/src/client/pages/about-misskey.vue
deleted file mode 100644
index d2c0ec0550..0000000000
--- a/src/client/pages/about-misskey.vue
+++ /dev/null
@@ -1,238 +0,0 @@
-<template>
-<div style="overflow: clip;">
- <FormBase class="znqjceqz">
- <div id="debug"></div>
- <section class="_debobigegoItem about">
- <div class="_debobigegoPanel panel" :class="{ playing: easterEggEngine != null }" ref="about">
- <img src="/static-assets/client/about-icon.png" alt="" class="icon" @load="iconLoaded" draggable="false" @click="gravity"/>
- <div class="misskey">Misskey</div>
- <div class="version">v{{ version }}</div>
- <span class="emoji" v-for="emoji in easterEggEmojis" :key="emoji.id" :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>
- </section>
- <section class="_debobigegoItem" style="text-align: center; padding: 0 16px;">
- {{ $ts._aboutMisskey.about }}<br><MkA class="_link" to="/docs/general/misskey">{{ $ts.learnMore }}</MkA>
- </section>
- <FormGroup>
- <FormLink to="https://github.com/misskey-dev/misskey" external>
- <template #icon><i class="fas fa-code"></i></template>
- {{ $ts._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 }}
- <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 }}
- <template #suffix>Patreon</template>
- </FormLink>
- </FormGroup>
- <FormGroup>
- <template #label>{{ $ts._aboutMisskey.contributors }}</template>
- <FormLink to="https://github.com/syuilo" external>@syuilo</FormLink>
- <FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink>
- <FormLink to="https://github.com/mei23" external>@mei23</FormLink>
- <FormLink to="https://github.com/acid-chicken" external>@acid-chicken</FormLink>
- <FormLink to="https://github.com/tamaina" external>@tamaina</FormLink>
- <FormLink to="https://github.com/rinsuki" external>@rinsuki</FormLink>
- <FormLink to="https://github.com/Xeltica" external>@Xeltica</FormLink>
- <FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink>
- <FormLink to="https://github.com/marihachi" external>@marihachi</FormLink>
- <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ $ts._aboutMisskey.allContributors }}</MkLink></template>
- </FormGroup>
- <FormGroup>
- <template #label><Mfm text="[jelly โค]"/> {{ $ts._aboutMisskey.patrons }}</template>
- <FormKeyValueView v-for="patron in patrons" :key="patron"><template #key>{{ patron }}</template></FormKeyValueView>
- <template #caption>{{ $ts._aboutMisskey.morePatrons }}</template>
- </FormGroup>
- </FormBase>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { version } from '@client/config';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import MkLink from '@client/components/link.vue';
-import { physics } from '@client/scripts/physics';
-import * as symbols from '@client/symbols';
-
-const patrons = [
- 'Satsuki Yanagi',
- 'noellabo',
- 'mametsuko',
- 'AureoleArk',
- 'Gargron',
- 'Nokotaro Takeda',
- 'Suji Yan',
- 'Hekovic',
- 'Gitmo Life Services',
- 'nenohi',
- 'naga_rus',
- 'Melilot',
- 'Efertone',
- 'oi_yekssim',
- 'nanami kan',
- 'motcha',
- 'dansup',
- 'Quinton Macejkovic',
- 'YUKIMOCHI',
- 'mewl hayabusa',
- 'makokunsan',
- 'Peter G.',
- 'Nesakko',
- 'regtan',
- '่ฆ‹ๅฝ“ใ‹ใชใฟ',
- 'natalie',
- 'Jerry',
- 'takimura',
- 'sikyosyounin',
- 'YuzuRyo61',
- 'sheeta.s',
- 'osapon',
- 'mkatze',
- 'CG',
- 'nafuchoco',
- 'Takumi Sugita',
- 'chidori ninokura',
- 'mydarkstar',
- 'kiritan',
- 'kabo2468y',
- 'weepjp',
- 'Liaizon Wakest',
- 'Steffen K9',
- 'Roujo',
- 'uroco @99',
- 'totokoro',
- 'public_yusuke',
- 'wara',
- 'S Y',
- 'Denshi',
- 'Osushimaru',
- 'ๅดๆตฅ',
- 'DignifiedSilence',
- 't_w',
-];
-
-export default defineComponent({
- components: {
- FormBase,
- FormGroup,
- FormLink,
- FormKeyValueView,
- MkLink,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.aboutMisskey,
- icon: null
- },
- version,
- patrons,
- easterEggReady: false,
- easterEggEmojis: [],
- easterEggEngine: null,
- }
- },
-
- beforeUnmount() {
- if (this.easterEggEngine) {
- this.easterEggEngine.stop();
- }
- },
-
- 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)],
- });
- }
-
- this.$nextTick(() => {
- this.easterEggReady = true;
- });
- },
-
- gravity() {
- if (!this.easterEggReady) return;
- this.easterEggReady = false;
- this.easterEggEngine = physics(this.$refs.about);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.znqjceqz {
- max-width: 800px;
- box-sizing: border-box;
- margin: 0 auto;
-
- > .about {
- > .panel {
- position: relative;
- text-align: center;
- padding: 16px;
-
- &.playing {
- &, * {
- user-select: none;
- }
-
- * {
- will-change: transform;
- }
-
- > .emoji {
- visibility: visible;
- }
- }
-
- > .icon {
- display: block;
- width: 100px;
- margin: 0 auto;
- border-radius: 16px;
- }
-
- > .misskey {
- margin: 0.75em auto 0 auto;
- width: max-content;
- }
-
- > .version {
- margin: 0 auto;
- width: max-content;
- opacity: 0.5;
- }
-
- > .emoji {
- position: absolute;
- top: 0;
- left: 0;
- visibility: hidden;
-
- > .emoji {
- pointer-events: none;
- font-size: 24px;
- width: 24px;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/about.vue b/src/client/pages/about.vue
deleted file mode 100644
index 2c580c293a..0000000000
--- a/src/client/pages/about.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<template>
-<FormBase>
- <div class="_debobigegoItem">
- <div class="_debobigegoPanel fwhjspax">
- <img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
- <span class="name">{{ $instance.name || host }}</span>
- </div>
- </div>
-
- <FormTextarea readonly :value="$instance.description">
- </FormTextarea>
-
- <FormGroup>
- <FormKeyValueView>
- <template #key>Misskey</template>
- <template #value>v{{ version }}</template>
- </FormKeyValueView>
- <FormLink to="/about-misskey">{{ $ts.aboutMisskey }}</FormLink>
- </FormGroup>
-
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts.administrator }}</template>
- <template #value>{{ $instance.maintainerName }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.contact }}</template>
- <template #value>{{ $instance.maintainerEmail }}</template>
- </FormKeyValueView>
- </FormGroup>
-
- <FormLink v-if="$instance.tosUrl" :to="$instance.tosUrl" external>{{ $ts.tos }}</FormLink>
-
- <FormSuspense :p="initStats">
- <FormGroup>
- <template #label>{{ $ts.statistics }}</template>
- <FormKeyValueView>
- <template #key>{{ $ts.users }}</template>
- <template #value>{{ number(stats.originalUsersCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.notes }}</template>
- <template #value>{{ number(stats.originalNotesCount) }}</template>
- </FormKeyValueView>
- </FormGroup>
- </FormSuspense>
-
- <FormGroup>
- <template #label>Well-known resources</template>
- <FormLink :to="`/.well-known/host-meta`" external>host-meta</FormLink>
- <FormLink :to="`/.well-known/host-meta.json`" external>host-meta.json</FormLink>
- <FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink>
- <FormLink :to="`/robots.txt`" external>robots.txt</FormLink>
- <FormLink :to="`/manifest.json`" external>manifest.json</FormLink>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { version, instanceName } from '@client/config';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-import * as symbols from '@client/symbols';
-import { host } from '@client/config';
-
-export default defineComponent({
- components: {
- FormBase,
- FormGroup,
- FormLink,
- FormKeyValueView,
- FormTextarea,
- FormSuspense,
- },
-
- 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;
- })
- }
- },
-
- methods: {
- number
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.fwhjspax {
- padding: 16px;
- text-align: center;
-
- > .icon {
- display: block;
- margin: auto;
- height: 64px;
- border-radius: 8px;
- }
-
- > .name {
- display: block;
- margin-top: 12px;
- }
-}
-</style>
diff --git a/src/client/pages/admin/abuses.vue b/src/client/pages/admin/abuses.vue
deleted file mode 100644
index 29da8cc2c5..0000000000
--- a/src/client/pages/admin/abuses.vue
+++ /dev/null
@@ -1,170 +0,0 @@
-<template>
-<div class="lcixvhis">
- <div class="_section reports">
- <div class="_content">
- <div class="inputs" style="display: flex;">
- <MkSelect v-model="state" style="margin: 0; flex: 1;">
- <template #label>{{ $ts.state }}</template>
- <option value="all">{{ $ts.all }}</option>
- <option value="unresolved">{{ $ts.unresolved }}</option>
- <option value="resolved">{{ $ts.resolved }}</option>
- </MkSelect>
- <MkSelect v-model="targetUserOrigin" style="margin: 0; flex: 1;">
- <template #label>{{ $ts.targetUserOrigin }}</template>
- <option value="combined">{{ $ts.all }}</option>
- <option value="local">{{ $ts.local }}</option>
- <option value="remote">{{ $ts.remote }}</option>
- </MkSelect>
- <MkSelect v-model="reporterOrigin" style="margin: 0; flex: 1;">
- <template #label>{{ $ts.reporterOrigin }}</template>
- <option value="combined">{{ $ts.all }}</option>
- <option value="local">{{ $ts.local }}</option>
- <option value="remote">{{ $ts.remote }}</option>
- </MkSelect>
- </div>
- <!-- TODO
- <div class="inputs" style="display: flex; padding-top: 1.2em;">
- <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()">
- <span>{{ $ts.username }}</span>
- </MkInput>
- <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'">
- <span>{{ $ts.host }}</span>
- </MkInput>
- </div>
- -->
-
- <MkPagination :pagination="pagination" #default="{items}" ref="reports" style="margin-top: var(--margin);">
- <div class="bcekxzvu _card _gap" v-for="report in items" :key="report.id">
- <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 @click="resolve(report)" primary v-if="!report.resolved">{{ $ts.abuseMarkAsResolved }}</MkButton>
- </div>
- </div>
- </MkPagination>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { parseAcct } from '@/misc/acct';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import { acct } from '@client/filters/user';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkPagination,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.abuseReports,
- icon: 'fas fa-exclamation-circle',
- bg: 'var(--bg)',
- },
- searchUsername: '',
- searchHost: '',
- state: 'unresolved',
- reporterOrigin: 'combined',
- targetUserOrigin: 'combined',
- pagination: {
- endpoint: 'admin/abuse-user-reports',
- limit: 10,
- params: () => ({
- 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);
- });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.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/src/client/pages/admin/ads.vue b/src/client/pages/admin/ads.vue
deleted file mode 100644
index 4d39bb4e40..0000000000
--- a/src/client/pages/admin/ads.vue
+++ /dev/null
@@ -1,138 +0,0 @@
-<template>
-<div class="uqshojas">
- <section class="_card _gap ads" v-for="ad in ads">
- <div class="_content ad">
- <MkAd v-if="ad.url" :specify="ad"/>
- <MkInput v-model="ad.url" type="url">
- <template #label>URL</template>
- </MkInput>
- <MkInput v-model="ad.imageUrl">
- <template #label>{{ $ts.imageUrl }}</template>
- </MkInput>
- <div style="margin: 32px 0;">
- <MkRadio v-model="ad.place" value="square">square</MkRadio>
- <MkRadio v-model="ad.place" value="horizontal">horizontal</MkRadio>
- <MkRadio v-model="ad.place" value="horizontal-big">horizontal-big</MkRadio>
- </div>
- <!--
- <div style="margin: 32px 0;">
- {{ $ts.priority }}
- <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio>
- <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio>
- <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio>
- </div>
- -->
- <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>
- <MkTextarea v-model="ad.memo">
- <template #label>{{ $ts.memo }}</template>
- </MkTextarea>
- <div class="buttons">
- <MkButton class="button" inline @click="save(ad)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
- <MkButton class="button" inline @click="remove(ad)" danger><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
- </div>
- </div>
- </section>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkRadio from '@client/components/form/radio.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkTextarea,
- MkRadio,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.ads,
- icon: 'fas fa-audio-description',
- bg: 'var(--bg)',
- actions: [{
- asFullButton: true,
- icon: 'fas fa-plus',
- text: this.$ts.add,
- handler: this.add,
- }],
- },
- ads: [],
- }
- },
-
- created() {
- os.api('admin/ad/list').then(ads => {
- this.ads = ads;
- });
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- add() {
- this.ads.unshift({
- id: null,
- memo: '',
- place: 'square',
- priority: 'middle',
- ratio: 1,
- url: '',
- imageUrl: null,
- expiresAt: null,
- });
- },
-
- remove(ad) {
- os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: ad.url }),
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
- this.ads = this.ads.filter(x => x != ad);
- os.apiWithDialog('admin/ad/delete', {
- id: ad.id
- });
- });
- },
-
- save(ad) {
- if (ad.id == null) {
- os.apiWithDialog('admin/ad/create', {
- ...ad,
- expiresAt: new Date(ad.expiresAt).getTime()
- });
- } else {
- os.apiWithDialog('admin/ad/update', {
- ...ad,
- expiresAt: new Date(ad.expiresAt).getTime()
- });
- }
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.uqshojas {
- margin: var(--margin);
-}
-</style>
diff --git a/src/client/pages/admin/announcements.vue b/src/client/pages/admin/announcements.vue
deleted file mode 100644
index 4ace515b0b..0000000000
--- a/src/client/pages/admin/announcements.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<template>
-<div class="ztgjmzrw">
- <section class="_card _gap announcements" v-for="announcement in announcements">
- <div class="_content announcement">
- <MkInput v-model="announcement.title">
- <template #label>{{ $ts.title }}</template>
- </MkInput>
- <MkTextarea v-model="announcement.text">
- <template #label>{{ $ts.text }}</template>
- </MkTextarea>
- <MkInput v-model="announcement.imageUrl">
- <template #label>{{ $ts.imageUrl }}</template>
- </MkInput>
- <p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p>
- <div class="buttons">
- <MkButton class="button" inline @click="save(announcement)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
- <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
- </div>
- </div>
- </section>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkTextarea,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.announcements,
- icon: 'fas fa-broadcast-tower',
- bg: 'var(--bg)',
- actions: [{
- asFullButton: true,
- icon: 'fas fa-plus',
- text: this.$ts.add,
- handler: this.add,
- }],
- },
- announcements: [],
- }
- },
-
- created() {
- os.api('admin/announcements/list').then(announcements => {
- this.announcements = announcements;
- });
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- add() {
- this.announcements.unshift({
- id: null,
- title: '',
- text: '',
- imageUrl: null
- });
- },
-
- remove(announcement) {
- os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: announcement.title }),
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
- this.announcements = this.announcements.filter(x => x != announcement);
- os.api('admin/announcements/delete', announcement);
- });
- },
-
- save(announcement) {
- if (announcement.id == null) {
- os.api('admin/announcements/create', announcement).then(() => {
- os.dialog({
- type: 'success',
- text: this.$ts.saved
- });
- }).catch(e => {
- os.dialog({
- type: 'error',
- text: e
- });
- });
- } else {
- os.api('admin/announcements/update', announcement).then(() => {
- os.dialog({
- type: 'success',
- text: this.$ts.saved
- });
- }).catch(e => {
- os.dialog({
- type: 'error',
- text: e
- });
- });
- }
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.ztgjmzrw {
- margin: var(--margin);
-}
-</style>
diff --git a/src/client/pages/admin/bot-protection.vue b/src/client/pages/admin/bot-protection.vue
deleted file mode 100644
index 731f114cc2..0000000000
--- a/src/client/pages/admin/bot-protection.vue
+++ /dev/null
@@ -1,138 +0,0 @@
-<template>
-<FormBase>
- <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>
-
- <template v-if="provider === 'hcaptcha'">
- <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <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 class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <div class="_debobigegoLabel">{{ $ts.preview }}</div>
- <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);">
- <MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
- </div>
- </div>
- </template>
- <template v-else-if="provider === 'recaptcha'">
- <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <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" class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <div class="_debobigegoLabel">{{ $ts.preview }}</div>
- <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);">
- <MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/>
- </div>
- </div>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormRadios from '@client/components/debobigego/radios.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormRadios,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormInfo,
- FormSuspense,
- MkCaptcha: defineAsyncComponent(() => import('@client/components/captcha.vue')),
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.botProtection,
- icon: 'fas fa-shield-alt'
- },
- provider: null,
- enableHcaptcha: false,
- hcaptchaSiteKey: null,
- hcaptchaSecretKey: null,
- enableRecaptcha: false,
- recaptchaSiteKey: null,
- recaptchaSecretKey: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableHcaptcha = meta.enableHcaptcha;
- this.hcaptchaSiteKey = meta.hcaptchaSiteKey;
- this.hcaptchaSecretKey = meta.hcaptchaSecretKey;
- this.enableRecaptcha = meta.enableRecaptcha;
- this.recaptchaSiteKey = meta.recaptchaSiteKey;
- this.recaptchaSecretKey = meta.recaptchaSecretKey;
-
- this.provider = this.enableHcaptcha ? 'hcaptcha' : this.enableRecaptcha ? 'recaptcha' : null;
-
- this.$watch(() => this.provider, () => {
- this.enableHcaptcha = this.provider === 'hcaptcha';
- this.enableRecaptcha = this.provider === 'recaptcha';
- });
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableHcaptcha: this.enableHcaptcha,
- hcaptchaSiteKey: this.hcaptchaSiteKey,
- hcaptchaSecretKey: this.hcaptchaSecretKey,
- enableRecaptcha: this.enableRecaptcha,
- recaptchaSiteKey: this.recaptchaSiteKey,
- recaptchaSecretKey: this.recaptchaSecretKey,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/database.vue b/src/client/pages/admin/database.vue
deleted file mode 100644
index ffbeed8b30..0000000000
--- a/src/client/pages/admin/database.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="databasePromiseFactory" v-slot="{ result: database }">
- <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>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import bytes from '@client/filters/bytes';
-import number from '@client/filters/number';
-
-export default defineComponent({
- components: {
- FormSuspense,
- FormKeyValueView,
- FormBase,
- FormGroup,
- FormLink,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.database,
- icon: 'fas fa-database',
- bg: 'var(--bg)',
- },
- databasePromiseFactory: () => os.api('admin/get-table-stats', {}).then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)),
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- bytes, number,
- }
-});
-</script>
diff --git a/src/client/pages/admin/email-settings.vue b/src/client/pages/admin/email-settings.vue
deleted file mode 100644
index ebf724fcdd..0000000000
--- a/src/client/pages/admin/email-settings.vue
+++ /dev/null
@@ -1,128 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch>
-
- <template v-if="enableEmail">
- <FormInput v-model="email" type="email">
- <span>{{ $ts.emailAddress }}</span>
- </FormInput>
-
- <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <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 @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.emailServer,
- icon: 'fas fa-envelope',
- bg: 'var(--bg)',
- },
- enableEmail: false,
- email: null,
- smtpSecure: false,
- smtpHost: '',
- smtpPort: 0,
- smtpUser: '',
- smtpPass: '',
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableEmail = meta.enableEmail;
- this.email = meta.email;
- this.smtpSecure = meta.smtpSecure;
- this.smtpHost = meta.smtpHost;
- this.smtpPort = meta.smtpPort;
- this.smtpUser = meta.smtpUser;
- this.smtpPass = meta.smtpPass;
- },
-
- async testEmail() {
- const { canceled, result: destination } = await os.dialog({
- title: this.$ts.destination,
- input: {
- placeholder: this.$instance.maintainerEmail
- }
- });
- if (canceled) return;
- os.apiWithDialog('admin/send-email', {
- to: destination,
- subject: 'Test email',
- text: 'Yo'
- });
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableEmail: this.enableEmail,
- email: this.email,
- smtpSecure: this.smtpSecure,
- smtpHost: this.smtpHost,
- smtpPort: this.smtpPort,
- smtpUser: this.smtpUser,
- smtpPass: this.smtpPass,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/emoji-edit-dialog.vue b/src/client/pages/admin/emoji-edit-dialog.vue
deleted file mode 100644
index 4854c69884..0000000000
--- a/src/client/pages/admin/emoji-edit-dialog.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-<template>
-<XModalWindow ref="dialog"
- :width="370"
- :with-ok-button="true"
- @close="$refs.dialog.close()"
- @closed="$emit('closed')"
- @ok="ok()"
->
- <template #header>:{{ emoji.name }}:</template>
-
- <div class="_monolithic_">
- <div class="yigymqpb _section">
- <img :src="emoji.url" class="img"/>
- <MkInput class="_formBlock" v-model="name">
- <template #label>{{ $ts.name }}</template>
- </MkInput>
- <MkInput class="_formBlock" v-model="category" :datalist="categories">
- <template #label>{{ $ts.category }}</template>
- </MkInput>
- <MkInput class="_formBlock" v-model="aliases">
- <template #label>{{ $ts.tags }}</template>
- <template #caption>{{ $ts.setMultipleBySeparatingWithSpace }}</template>
- </MkInput>
- <MkButton danger @click="del()"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton>
- </div>
- </div>
-</XModalWindow>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XModalWindow from '@client/components/ui/modal-window.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-import { unique } from '../../../prelude/array';
-
-export default defineComponent({
- components: {
- XModalWindow,
- MkButton,
- MkInput,
- },
-
- props: {
- emoji: {
- required: true,
- }
- },
-
- emits: ['done', 'closed'],
-
- data() {
- return {
- name: this.emoji.name,
- category: this.emoji.category,
- aliases: this.emoji.aliases?.join(' '),
- categories: [],
- }
- },
-
- created() {
- os.api('meta', { detail: false }).then(({ emojis }) => {
- this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
- });
- },
-
- methods: {
- ok() {
- this.update();
- },
-
- async update() {
- await os.apiWithDialog('admin/emoji/update', {
- id: this.emoji.id,
- name: this.name,
- category: this.category,
- aliases: this.aliases.split(' '),
- });
-
- this.$emit('done', {
- updated: {
- name: this.name,
- category: this.category,
- aliases: this.aliases.split(' '),
- }
- });
- this.$refs.dialog.close();
- },
-
- async del() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: this.emoji.name }),
- showCancelButton: true
- });
- if (canceled) return;
-
- os.api('admin/emoji/remove', {
- id: this.emoji.id
- }).then(() => {
- this.$emit('done', {
- deleted: true
- });
- this.$refs.dialog.close();
- });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.yigymqpb {
- > .img {
- display: block;
- height: 64px;
- margin: 0 auto;
- }
-}
-</style>
diff --git a/src/client/pages/admin/emojis.vue b/src/client/pages/admin/emojis.vue
deleted file mode 100644
index 5876db349e..0000000000
--- a/src/client/pages/admin/emojis.vue
+++ /dev/null
@@ -1,263 +0,0 @@
-<template>
-<div class="ogwlenmc">
- <div class="local" v-if="tab === 'local'">
- <MkInput v-model="query" :debounce="true" type="search" style="margin: var(--margin);">
- <template #prefix><i class="fas fa-search"></i></template>
- <template #label>{{ $ts.search }}</template>
- </MkInput>
- <MkPagination :pagination="pagination" ref="emojis">
- <template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
- <template #default="{items}">
- <div class="ldhfsamy">
- <button class="emoji _panel _button" v-for="emoji in items" :key="emoji.id" @click="edit(emoji)">
- <img :src="emoji.url" class="img" :alt="emoji.name"/>
- <div class="body">
- <div class="name _monospace">{{ emoji.name }}</div>
- <div class="info">{{ emoji.category }}</div>
- </div>
- </button>
- </div>
- </template>
- </MkPagination>
- </div>
-
- <div class="remote" v-else-if="tab === 'remote'">
- <MkInput v-model="queryRemote" :debounce="true" type="search" style="margin: var(--margin);">
- <template #prefix><i class="fas fa-search"></i></template>
- <template #label>{{ $ts.search }}</template>
- </MkInput>
- <MkInput v-model="host" :debounce="true" style="margin: var(--margin);">
- <template #label>{{ $ts.host }}</template>
- </MkInput>
- <MkPagination :pagination="remotePagination" ref="remoteEmojis">
- <template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
- <template #default="{items}">
- <div class="ldhfsamy">
- <div class="emoji _panel _button" v-for="emoji in items" :key="emoji.id" @click="remoteMenu(emoji, $event)">
- <img :src="emoji.url" class="img" :alt="emoji.name"/>
- <div class="body">
- <div class="name _monospace">{{ emoji.name }}</div>
- <div class="info">{{ emoji.host }}</div>
- </div>
- </div>
- </div>
- </template>
- </MkPagination>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, toRef } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkTab from '@client/components/tab.vue';
-import { selectFile } from '@client/scripts/select-file';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkTab,
- MkButton,
- MkInput,
- MkPagination,
- },
-
- emits: ['info'],
-
- 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,
- }],
- 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
- }))
- },
- }
- },
-
- async mounted() {
- this.$emit('info', toRef(this, symbols.PAGE_INFO));
- },
-
- methods: {
- async add(e) {
- const files = await selectFile(e.currentTarget || e.target, null, true);
-
- const promise = Promise.all(files.map(file => os.api('admin/emoji/add', {
- fileId: file.id,
- })));
- promise.then(() => {
- this.$refs.emojis.reload();
- });
- os.promiseDialog(promise);
- },
-
- 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');
- },
-
- im(emoji) {
- os.apiWithDialog('admin/emoji/copy', {
- emojiId: emoji.id,
- });
- },
-
- 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);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.ogwlenmc {
- > .local {
- .empty {
- margin: var(--margin);
- }
-
- .ldhfsamy {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
- grid-gap: 12px;
- margin: var(--margin);
-
- > .emoji {
- display: flex;
- align-items: center;
- padding: 12px;
- text-align: left;
-
- &:hover {
- color: var(--accent);
- }
-
- > .img {
- width: 42px;
- height: 42px;
- }
-
- > .body {
- padding: 0 0 0 8px;
- white-space: nowrap;
- overflow: hidden;
-
- > .name {
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- > .info {
- opacity: 0.5;
- text-overflow: ellipsis;
- overflow: hidden;
- }
- }
- }
- }
- }
-
- > .remote {
- .empty {
- margin: var(--margin);
- }
-
- .ldhfsamy {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
- grid-gap: 12px;
- margin: var(--margin);
-
- > .emoji {
- display: flex;
- align-items: center;
- padding: 12px;
- text-align: left;
-
- &:hover {
- color: var(--accent);
- }
-
- > .img {
- width: 32px;
- height: 32px;
- }
-
- > .body {
- padding: 0 0 0 8px;
- white-space: nowrap;
- overflow: hidden;
-
- > .name {
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- > .info {
- opacity: 0.5;
- font-size: 90%;
- text-overflow: ellipsis;
- overflow: hidden;
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/admin/file-dialog.vue b/src/client/pages/admin/file-dialog.vue
deleted file mode 100644
index 02d83e5022..0000000000
--- a/src/client/pages/admin/file-dialog.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-<template>
-<XModalWindow ref="dialog"
- :width="370"
- @close="$refs.dialog.close()"
- @closed="$emit('closed')"
->
- <template #header v-if="file">{{ file.name }}</template>
- <div class="cxqhhsmd" v-if="file">
- <div class="_section">
- <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
- <div class="info">
- <span style="margin-right: 1em;">{{ file.type }}</span>
- <span>{{ bytes(file.size) }}</span>
- <MkTime :time="file.createdAt" mode="detail" style="display: block;"/>
- </div>
- </div>
- <div class="_section">
- <div class="_content">
- <MkSwitch @update:modelValue="toggleIsSensitive" v-model="isSensitive">NSFW</MkSwitch>
- </div>
- </div>
- <div class="_section">
- <div class="_content">
- <MkButton full @click="showUser"><i class="fas fa-external-link-square-alt"></i> {{ $ts.user }}</MkButton>
- <MkButton full danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton>
- </div>
- </div>
- <div class="_section" v-if="info">
- <details class="_content rawdata">
- <pre><code>{{ JSON.stringify(info, null, 2) }}</code></pre>
- </details>
- </div>
- </div>
-</XModalWindow>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import XModalWindow from '@client/components/ui/modal-window.vue';
-import MkDriveFileThumbnail from '@client/components/drive-file-thumbnail.vue';
-import Progress from '@client/scripts/loading';
-import bytes from '@client/filters/bytes';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkButton,
- MkSwitch,
- XModalWindow,
- MkDriveFileThumbnail,
- },
-
- props: {
- fileId: {
- required: true,
- }
- },
-
- emits: ['closed'],
-
- data() {
- return {
- file: null,
- info: null,
- isSensitive: false,
- };
- },
-
- created() {
- this.fetch();
- },
-
- methods: {
- async fetch() {
- Progress.start();
- this.file = await os.api('drive/files/show', { fileId: this.fileId });
- this.info = await os.api('admin/drive/show-file', { fileId: this.fileId });
- this.isSensitive = this.file.isSensitive;
- Progress.done();
- },
-
- showUser() {
- os.pageWindow(`/user-info/${this.file.userId}`);
- },
-
- async del() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: this.file.name }),
- showCancelButton: true
- });
- if (canceled) return;
-
- os.apiWithDialog('drive/files/delete', {
- fileId: this.file.id
- });
- },
-
- async toggleIsSensitive(v) {
- await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v });
- this.isSensitive = v;
- },
-
- bytes
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.cxqhhsmd {
- > ._section {
- > .thumbnail {
- height: 150px;
- max-width: 100%;
- }
-
- > .info {
- text-align: center;
- margin-top: 8px;
- }
-
- > .rawdata {
- overflow: auto;
- }
- }
-}
-</style>
diff --git a/src/client/pages/admin/files-settings.vue b/src/client/pages/admin/files-settings.vue
deleted file mode 100644
index 8aefa9e90d..0000000000
--- a/src/client/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 @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/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/src/client/pages/admin/files.vue b/src/client/pages/admin/files.vue
deleted file mode 100644
index 55189cfd84..0000000000
--- a/src/client/pages/admin/files.vue
+++ /dev/null
@@ -1,209 +0,0 @@
-<template>
-<div class="xrmjdkdw">
- <MkContainer :foldable="true" class="lookup">
- <template #header><i class="fas fa-search"></i> {{ $ts.lookup }}</template>
- <div class="xrmjdkdw-lookup">
- <MkInput class="item" v-model="q" type="text" @enter="find()">
- <template #label>{{ $ts.fileIdOrUrl }}</template>
- </MkInput>
- <MkButton @click="find()" primary><i class="fas fa-search"></i> {{ $ts.lookup }}</MkButton>
- </div>
- </MkContainer>
-
- <div class="_section">
- <div class="_content">
- <div class="inputs" style="display: flex;">
- <MkSelect v-model="origin" style="margin: 0; flex: 1;">
- <template #label>{{ $ts.instance }}</template>
- <option value="combined">{{ $ts.all }}</option>
- <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'">
- <template #label>{{ $ts.host }}</template>
- </MkInput>
- </div>
- <div class="inputs" style="display: flex; padding-top: 1.2em;">
- <MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;">
- <template #label>MIME type</template>
- </MkInput>
- </div>
- <MkPagination :pagination="pagination" #default="{items}" class="urempief" ref="files">
- <button class="file _panel _button _gap" v-for="file in items" :key="file.id" @click="show(file, $event)">
- <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
- <div class="body">
- <div>
- <small style="opacity: 0.7;">{{ file.name }}</small>
- </div>
- <div>
- <MkAcct v-if="file.user" :user="file.user"/>
- <div v-else>{{ $ts.system }}</div>
- </div>
- <div>
- <span style="margin-right: 1em;">{{ file.type }}</span>
- <span>{{ bytes(file.size) }}</span>
- </div>
- <div>
- <span>{{ $ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
- </div>
- </div>
- </button>
- </MkPagination>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkDriveFileThumbnail from '@client/components/drive-file-thumbnail.vue';
-import bytes from '@client/filters/bytes';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkPagination,
- MkContainer,
- MkDriveFileThumbnail,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.files,
- icon: 'fas fa-cloud',
- bg: 'var(--bg)',
- actions: [{
- text: this.$ts.clearCachedFiles,
- icon: 'fas fa-trash-alt',
- handler: this.clear
- }]
- },
- q: null,
- origin: 'local',
- type: null,
- searchHost: '',
- pagination: {
- endpoint: 'admin/drive/files',
- limit: 10,
- params: () => ({
- type: (this.type && this.type !== '') ? this.type : null,
- origin: this.origin,
- hostname: (this.hostname && this.hostname !== '') ? this.hostname : 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.dialog({
- type: 'warning',
- text: this.$ts.clearCachedFilesConfirm,
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
-
- os.apiWithDialog('admin/drive/clean-remote-files', {});
- });
- },
-
- show(file, ev) {
- os.popup(import('./file-dialog.vue'), {
- fileId: file.id
- }, {}, 'closed');
- },
-
- find() {
- os.api('admin/drive/show-file', this.q.startsWith('http://') || this.q.startsWith('https://') ? { url: this.q.trim() } : { fileId: this.q.trim() }).then(file => {
- this.show(file);
- }).catch(e => {
- if (e.code === 'NO_SUCH_FILE') {
- os.dialog({
- type: 'error',
- text: this.$ts.notFound
- });
- }
- });
- },
-
- bytes
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.xrmjdkdw {
- margin: var(--margin);
-
- > .lookup {
- margin-bottom: 16px;
- }
-
- .urempief {
- margin-top: var(--margin);
-
- > .file {
- display: flex;
- width: 100%;
- box-sizing: border-box;
- text-align: left;
- align-items: center;
-
- &:hover {
- color: var(--accent);
- }
-
- > .thumbnail {
- width: 128px;
- height: 128px;
- }
-
- > .body {
- margin-left: 0.3em;
- padding: 8px;
- flex: 1;
-
- @media (max-width: 500px) {
- font-size: 14px;
- }
- }
- }
- }
-}
-
-.xrmjdkdw-lookup {
- padding: 16px;
-
- > .item {
- margin-bottom: 16px;
- }
-}
-</style>
diff --git a/src/client/pages/admin/index.vue b/src/client/pages/admin/index.vue
deleted file mode 100644
index 28157ff05a..0000000000
--- a/src/client/pages/admin/index.vue
+++ /dev/null
@@ -1,388 +0,0 @@
-<template>
-<div class="hiyeyicy" :class="{ wide: !narrow }" ref="el">
- <div class="nav" v-if="!narrow || page == null">
- <MkHeader :info="header"></MkHeader>
-
- <MkSpacer :content-max="700">
- <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>
-
- <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu>
- </div>
- </MkSpacer>
- </div>
- <div class="main">
- <MkStickyContainer>
- <template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template>
- <component :is="component" :key="page" @info="onInfo" v-bind="pageProps"/>
- </MkStickyContainer>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue';
-import { i18n } from '@client/i18n';
-import MkSuperMenu from '@client/components/ui/super-menu.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import MkInfo from '@client/components/ui/info.vue';
-import { scroll } from '@client/scripts/scroll';
-import { instance } from '@client/instance';
-import * as symbols from '@client/symbols';
-import * as os from '@client/os';
-import { lookupUser } from '@client/scripts/lookup-user';
-
-export default defineComponent({
- components: {
- FormBase,
- MkSuperMenu,
- FormGroup,
- FormButton,
- MkInfo,
- },
-
- provide: {
- shouldOmitHeaderTitle: false,
- },
-
- props: {
- initialPage: {
- type: String,
- required: false
- }
- },
-
- setup(props, context) {
- const indexInfo = {
- title: i18n.locale.controlPanel,
- icon: 'fas fa-cog',
- bg: 'var(--bg)',
- hideHeader: true,
- };
- const INFO = ref(indexInfo);
- const childInfo = ref(null);
- const page = ref(props.initialPage);
- const narrow = ref(false);
- const view = ref(null);
- const el = ref(null);
- const onInfo = (viewInfo) => {
- if (isRef(viewInfo)) {
- watch(viewInfo, () => {
- childInfo.value = viewInfo.value;
- }, { immediate: true });
- } else {
- childInfo.value = viewInfo;
- }
- };
- const pageProps = ref({});
-
- const isEmpty = (x: any) => x == null || x == '';
-
- const noMaintainerInformation = ref(false);
- const noBotProtection = ref(false);
-
- os.api('meta', { detail: true }).then(meta => {
- // TODO: ่จญๅฎšใŒๅฎŒไบ†ใ—ใฆใ‚‚ๆฎ‹ใฃใŸใพใพใซใชใ‚‹ใฎใงใ€ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใงmetaๆ›ดๆ–ฐใ‚คใƒ™ใƒณใƒˆใ‚’ๅ—ใ‘ๅ–ใฃใฆใ‚ˆใ—ใชใซๆ›ดๆ–ฐใ™ใ‚‹
- noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail);
- noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha;
- });
-
- const menuDef = computed(() => [{
- title: i18n.locale.quickAction,
- items: [{
- type: 'button',
- icon: 'fas fa-search',
- text: i18n.locale.lookup,
- action: lookup,
- }, ...(instance.disableRegistration ? [{
- type: 'button',
- icon: 'fas fa-user',
- text: i18n.locale.invite,
- action: invite,
- }] : [])],
- }, {
- title: i18n.locale.administration,
- items: [{
- icon: 'fas fa-tachometer-alt',
- text: i18n.locale.dashboard,
- to: '/admin/overview',
- active: page.value === 'overview',
- }, {
- icon: 'fas fa-users',
- text: i18n.locale.users,
- to: '/admin/users',
- active: page.value === 'users',
- }, {
- icon: 'fas fa-laugh',
- text: i18n.locale.customEmojis,
- to: '/admin/emojis',
- active: page.value === 'emojis',
- }, {
- icon: 'fas fa-globe',
- text: i18n.locale.federation,
- to: '/admin/federation',
- active: page.value === 'federation',
- }, {
- icon: 'fas fa-clipboard-list',
- text: i18n.locale.jobQueue,
- to: '/admin/queue',
- active: page.value === 'queue',
- }, {
- icon: 'fas fa-cloud',
- text: i18n.locale.files,
- to: '/admin/files',
- active: page.value === 'files',
- }, {
- icon: 'fas fa-broadcast-tower',
- text: i18n.locale.announcements,
- to: '/admin/announcements',
- active: page.value === 'announcements',
- }, {
- icon: 'fas fa-audio-description',
- text: i18n.locale.ads,
- to: '/admin/ads',
- active: page.value === 'ads',
- }, {
- icon: 'fas fa-exclamation-circle',
- text: i18n.locale.abuseReports,
- to: '/admin/abuses',
- active: page.value === 'abuses',
- }],
- }, {
- title: i18n.locale.settings,
- items: [{
- icon: 'fas fa-cog',
- text: i18n.locale.general,
- 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',
- active: page.value === 'email-settings',
- }, {
- icon: 'fas fa-cloud',
- text: i18n.locale.objectStorage,
- to: '/admin/object-storage',
- active: page.value === 'object-storage',
- }, {
- icon: 'fas fa-lock',
- text: i18n.locale.security,
- 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',
- active: page.value === 'relays',
- }, {
- icon: 'fas fa-share-alt',
- text: i18n.locale.integration,
- to: '/admin/integrations',
- active: page.value === 'integrations',
- }, {
- icon: 'fas fa-ban',
- text: i18n.locale.instanceBlocking,
- to: '/admin/instance-block',
- active: page.value === 'instance-block',
- }, {
- icon: 'fas fa-ghost',
- text: i18n.locale.proxyAccount,
- to: '/admin/proxy-account',
- active: page.value === 'proxy-account',
- }, {
- icon: 'fas fa-cogs',
- text: i18n.locale.other,
- to: '/admin/other-settings',
- active: page.value === 'other-settings',
- }],
- }, {
- title: i18n.locale.info,
- items: [{
- icon: 'fas fa-database',
- text: i18n.locale.database,
- to: '/admin/database',
- active: page.value === 'database',
- }],
- }]);
- const component = computed(() => {
- if (page.value == null) return null;
- switch (page.value) {
- case 'overview': return defineAsyncComponent(() => import('./overview.vue'));
- case 'users': return defineAsyncComponent(() => import('./users.vue'));
- case 'emojis': return defineAsyncComponent(() => import('./emojis.vue'));
- case 'federation': return defineAsyncComponent(() => import('../federation.vue'));
- case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
- case 'files': return defineAsyncComponent(() => import('./files.vue'));
- case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
- case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
- 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'));
- }
- });
-
- watch(component, () => {
- pageProps.value = {};
-
- nextTick(() => {
- scroll(el.value, { top: 0 });
- });
- }, { immediate: true });
-
- watch(() => props.initialPage, () => {
- if (props.initialPage == null && !narrow.value) {
- page.value = 'overview';
- } else {
- page.value = props.initialPage;
- if (props.initialPage == null) {
- INFO.value = indexInfo;
- }
- }
- });
-
- onMounted(() => {
- narrow.value = el.value.offsetWidth < 800;
- if (!narrow.value) {
- page.value = 'overview';
- }
- });
-
- const invite = () => {
- os.api('admin/invite').then(x => {
- os.dialog({
- type: 'info',
- text: x.code
- });
- }).catch(e => {
- os.dialog({
- type: 'error',
- text: e
- });
- });
- };
-
- const lookup = (ev) => {
- os.popupMenu([{
- text: i18n.locale.user,
- icon: 'fas fa-user',
- action: () => {
- lookupUser();
- }
- }, {
- text: i18n.locale.note,
- icon: 'fas fa-pencil-alt',
- action: () => {
- alert('TODO');
- }
- }, {
- text: i18n.locale.file,
- icon: 'fas fa-cloud',
- action: () => {
- alert('TODO');
- }
- }, {
- text: i18n.locale.instance,
- icon: 'fas fa-globe',
- action: () => {
- alert('TODO');
- }
- }], ev.currentTarget || ev.target);
- };
-
- return {
- [symbols.PAGE_INFO]: INFO,
- menuDef,
- header: {
- title: i18n.locale.controlPanel,
- },
- noMaintainerInformation,
- noBotProtection,
- page,
- narrow,
- view,
- el,
- onInfo,
- childInfo,
- pageProps,
- component,
- invite,
- lookup,
- };
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.hiyeyicy {
- &.wide {
- display: flex;
- margin: 0 auto;
- height: 100%;
-
- > .nav {
- width: 32%;
- max-width: 280px;
- box-sizing: border-box;
- border-right: solid 0.5px var(--divider);
- overflow: auto;
- height: 100%;
- }
-
- > .main {
- flex: 1;
- min-width: 0;
- }
- }
-
- > .nav {
- .lxpfedzu {
- > .info {
- margin: 16px 0;
- }
-
- > .banner {
- margin: 16px;
-
- > .icon {
- display: block;
- margin: auto;
- height: 42px;
- border-radius: 8px;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/admin/instance-block.vue b/src/client/pages/admin/instance-block.vue
deleted file mode 100644
index 105cdb4941..0000000000
--- a/src/client/pages/admin/instance-block.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormTextarea v-model="blockedHosts">
- <span>{{ $ts.blockedInstances }}</span>
- <template #desc>{{ $ts.blockedInstancesDescription }}</template>
- </FormTextarea>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.instanceBlocking,
- icon: 'fas fa-ban',
- bg: 'var(--bg)',
- },
- blockedHosts: '',
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.blockedHosts = meta.blockedHosts.join('\n');
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- blockedHosts: this.blockedHosts.split('\n') || [],
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/instance.vue b/src/client/pages/admin/instance.vue
deleted file mode 100644
index 5572fbbf75..0000000000
--- a/src/client/pages/admin/instance.vue
+++ /dev/null
@@ -1,321 +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 @click="deleteAllFiles()" style="margin: 0.5em 0 0.5em 0;"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton>
- </details>
- <details>
- <summary>{{ $ts.removeAllFollowing }}</summary>
- <MkButton @click="removeAllFollowing()" style="margin: 0.5em 0 0.5em 0;"><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 '@client/components/ui/modal-window.vue';
-import MkUsersDialog from '@client/components/users-dialog.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import MkInfo from '@client/components/ui/info.vue';
-import MkChart from '@client/components/chart.vue';
-import bytes from '@client/filters/bytes';
-import number from '@client/filters/number';
-import * as os from '@client/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() {
- os.modal(MkUsersDialog, {
- title: this.$ts.instanceFollowing,
- pagination: {
- endpoint: 'federation/following',
- limit: 10,
- params: {
- host: this.instance.host
- }
- },
- extract: item => item.follower
- });
- },
-
- showFollowers() {
- os.modal(MkUsersDialog, {
- title: this.$ts.instanceFollowers,
- pagination: {
- endpoint: 'federation/followers',
- limit: 10,
- params: {
- host: this.instance.host
- }
- },
- extract: item => item.followee
- });
- },
-
- showUsers() {
- os.modal(MkUsersDialog, {
- title: this.$ts.instanceUsers,
- pagination: {
- endpoint: 'federation/users',
- limit: 10,
- params: {
- host: this.instance.host
- }
- }
- });
- },
-
- 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/src/client/pages/admin/integrations-discord.vue b/src/client/pages/admin/integrations-discord.vue
deleted file mode 100644
index c33b24f17f..0000000000
--- a/src/client/pages/admin/integrations-discord.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableDiscordIntegration">
- {{ $ts.enable }}
- </FormSwitch>
-
- <template v-if="enableDiscordIntegration">
- <FormInfo>Callback URL: {{ `${url}/api/dc/cb` }}</FormInfo>
-
- <FormInput v-model="discordClientId">
- <template #prefix><i class="fas fa-key"></i></template>
- Client ID
- </FormInput>
-
- <FormInput v-model="discordClientSecret">
- <template #prefix><i class="fas fa-key"></i></template>
- Client Secret
- </FormInput>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormInfo,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'Discord',
- icon: 'fab fa-discord'
- },
- enableDiscordIntegration: false,
- discordClientId: null,
- discordClientSecret: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableDiscordIntegration = meta.enableDiscordIntegration;
- this.discordClientId = meta.discordClientId;
- this.discordClientSecret = meta.discordClientSecret;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableDiscordIntegration: this.enableDiscordIntegration,
- discordClientId: this.discordClientId,
- discordClientSecret: this.discordClientSecret,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/integrations-github.vue b/src/client/pages/admin/integrations-github.vue
deleted file mode 100644
index cdf85868ff..0000000000
--- a/src/client/pages/admin/integrations-github.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableGithubIntegration">
- {{ $ts.enable }}
- </FormSwitch>
-
- <template v-if="enableGithubIntegration">
- <FormInfo>Callback URL: {{ `${url}/api/gh/cb` }}</FormInfo>
-
- <FormInput v-model="githubClientId">
- <template #prefix><i class="fas fa-key"></i></template>
- Client ID
- </FormInput>
-
- <FormInput v-model="githubClientSecret">
- <template #prefix><i class="fas fa-key"></i></template>
- Client Secret
- </FormInput>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormInfo,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'GitHub',
- icon: 'fab fa-github'
- },
- enableGithubIntegration: false,
- githubClientId: null,
- githubClientSecret: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableGithubIntegration = meta.enableGithubIntegration;
- this.githubClientId = meta.githubClientId;
- this.githubClientSecret = meta.githubClientSecret;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableGithubIntegration: this.enableGithubIntegration,
- githubClientId: this.githubClientId,
- githubClientSecret: this.githubClientSecret,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/integrations-twitter.vue b/src/client/pages/admin/integrations-twitter.vue
deleted file mode 100644
index ed7d097d0a..0000000000
--- a/src/client/pages/admin/integrations-twitter.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableTwitterIntegration">
- {{ $ts.enable }}
- </FormSwitch>
-
- <template v-if="enableTwitterIntegration">
- <FormInfo>Callback URL: {{ `${url}/api/tw/cb` }}</FormInfo>
-
- <FormInput v-model="twitterConsumerKey">
- <template #prefix><i class="fas fa-key"></i></template>
- Consumer Key
- </FormInput>
-
- <FormInput v-model="twitterConsumerSecret">
- <template #prefix><i class="fas fa-key"></i></template>
- Consumer Secret
- </FormInput>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormInfo,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'Twitter',
- icon: 'fab fa-twitter'
- },
- enableTwitterIntegration: false,
- twitterConsumerKey: null,
- twitterConsumerSecret: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableTwitterIntegration = meta.enableTwitterIntegration;
- this.twitterConsumerKey = meta.twitterConsumerKey;
- this.twitterConsumerSecret = meta.twitterConsumerSecret;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableTwitterIntegration: this.enableTwitterIntegration,
- twitterConsumerKey: this.twitterConsumerKey,
- twitterConsumerSecret: this.twitterConsumerSecret,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/integrations.vue b/src/client/pages/admin/integrations.vue
deleted file mode 100644
index bdc2cec4d0..0000000000
--- a/src/client/pages/admin/integrations.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormLink to="/admin/integrations/twitter">
- <i class="fab fa-twitter"></i> Twitter
- <template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
- <FormLink to="/admin/integrations/github">
- <i class="fab fa-github"></i> GitHub
- <template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
- <FormLink to="/admin/integrations/discord">
- <i class="fab fa-discord"></i> Discord
- <template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormLink,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.integration,
- icon: 'fas fa-share-alt',
- bg: 'var(--bg)',
- },
- enableTwitterIntegration: false,
- enableGithubIntegration: false,
- enableDiscordIntegration: false,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableTwitterIntegration = meta.enableTwitterIntegration;
- this.enableGithubIntegration = meta.enableGithubIntegration;
- this.enableDiscordIntegration = meta.enableDiscordIntegration;
- },
- }
-});
-</script>
diff --git a/src/client/pages/admin/metrics.vue b/src/client/pages/admin/metrics.vue
deleted file mode 100644
index da36f6c688..0000000000
--- a/src/client/pages/admin/metrics.vue
+++ /dev/null
@@ -1,472 +0,0 @@
-<template>
-<div class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fas fa-microchip"></i> {{ $ts.cpuAndMemory }}</div>
- <div class="_debobigegoPanel xhexznfu">
- <div>
- <canvas :ref="cpumem"></canvas>
- </div>
- <div v-if="serverInfo">
- <div class="_table">
- <div class="_row">
- <div class="_cell"><div class="_label">MEM total</div>{{ bytes(serverInfo.mem.total) }}</div>
- <div class="_cell"><div class="_label">MEM used</div>{{ bytes(memUsage) }} ({{ (memUsage / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
- <div class="_cell"><div class="_label">MEM free</div>{{ bytes(serverInfo.mem.total - memUsage) }} ({{ ((serverInfo.mem.total - memUsage) / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
- </div>
- </div>
- </div>
- </div>
-</div>
-<div class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fas fa-hdd"></i> {{ $ts.disk }}</div>
- <div class="_debobigegoPanel xhexznfu">
- <div>
- <canvas :ref="disk"></canvas>
- </div>
- <div v-if="serverInfo">
- <div class="_table">
- <div class="_row">
- <div class="_cell"><div class="_label">Disk total</div>{{ bytes(serverInfo.fs.total) }}</div>
- <div class="_cell"><div class="_label">Disk used</div>{{ bytes(serverInfo.fs.used) }} ({{ (serverInfo.fs.used / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
- <div class="_cell"><div class="_label">Disk free</div>{{ bytes(serverInfo.fs.total - serverInfo.fs.used) }} ({{ ((serverInfo.fs.total - serverInfo.fs.used) / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
- </div>
- </div>
- </div>
- </div>
-</div>
-<div class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fas fa-exchange-alt"></i> {{ $ts.network }}</div>
- <div class="_debobigegoPanel xhexznfu">
- <div>
- <canvas :ref="net"></canvas>
- </div>
- <div v-if="serverInfo">
- <div class="_table">
- <div class="_row">
- <div class="_cell"><div class="_label">Interface</div>{{ serverInfo.net.interface }}</div>
- </div>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import {
- Chart,
- ArcElement,
- LineElement,
- BarElement,
- PointElement,
- BarController,
- LineController,
- CategoryScale,
- LinearScale,
- Legend,
- Title,
- Tooltip,
- SubTitle
-} from 'chart.js';
-import MkButton from '@client/components/ui/button.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import MkwFederation from '../../widgets/federation.vue';
-import { version, url } from '@client/config';
-import bytes from '@client/filters/bytes';
-import number from '@client/filters/number';
-import MkInstanceInfo from './instance.vue';
-
-Chart.register(
- ArcElement,
- LineElement,
- BarElement,
- PointElement,
- BarController,
- LineController,
- CategoryScale,
- LinearScale,
- Legend,
- Title,
- Tooltip,
- SubTitle
-);
-
-const alpha = (hex, a) => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
- const r = parseInt(result[1], 16);
- const g = parseInt(result[2], 16);
- const b = parseInt(result[3], 16);
- return `rgba(${r}, ${g}, ${b}, ${a})`;
-};
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkButton,
- MkSelect,
- MkInput,
- MkContainer,
- MkFolder,
- MkwFederation,
- },
-
- data() {
- return {
- version,
- url,
- stats: null,
- serverInfo: null,
- connection: null,
- queueConnection: markRaw(os.stream.useChannel('queueStats')),
- memUsage: 0,
- chartCpuMem: null,
- chartNet: null,
- jobs: [],
- logs: [],
- logLevel: 'all',
- logDomain: '',
- modLogs: [],
- dbInfo: null,
- overviewHeight: '1fr',
- queueHeight: '1fr',
- paused: false,
- }
- },
-
- computed: {
- gridColor() {
- // TODO: var(--panel)ใฎ่‰ฒใŒๆš—ใ„ใ‹ๆ˜Žใ‚‹ใ„ใ‹ใงๅˆคๅฎšใ™ใ‚‹
- return this.$store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
- },
- },
-
- mounted() {
- this.fetchJobs();
-
- Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
-
- os.api('admin/server-info', {}).then(res => {
- this.serverInfo = res;
-
- this.connection = markRaw(os.stream.useChannel('serverStats'));
- this.connection.on('stats', this.onStats);
- this.connection.on('statsLog', this.onStatsLog);
- this.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 150
- });
-
- this.$nextTick(() => {
- this.queueConnection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 200
- });
- });
- });
- },
-
- beforeUnmount() {
- if (this.connection) {
- this.connection.off('stats', this.onStats);
- this.connection.off('statsLog', this.onStatsLog);
- this.connection.dispose();
- }
- this.queueConnection.dispose();
- },
-
- methods: {
- cpumem(el) {
- if (this.chartCpuMem != null) return;
- this.chartCpuMem = markRaw(new Chart(el, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'CPU',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#86b300',
- backgroundColor: alpha('#86b300', 0.1),
- data: []
- }, {
- label: 'MEM (active)',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#935dbf',
- backgroundColor: alpha('#935dbf', 0.02),
- data: []
- }, {
- label: 'MEM (used)',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#935dbf',
- borderDash: [5, 5],
- fill: false,
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- x: {
- gridLines: {
- display: false,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false,
- }
- },
- y: {
- position: 'right',
- gridLines: {
- display: true,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false,
- max: 100
- }
- }
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
- },
-
- net(el) {
- if (this.chartNet != null) return;
- this.chartNet = markRaw(new Chart(el, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'In',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#94a029',
- backgroundColor: alpha('#94a029', 0.1),
- data: []
- }, {
- label: 'Out',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#ff9156',
- backgroundColor: alpha('#ff9156', 0.1),
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- x: {
- gridLines: {
- display: false,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false
- }
- },
- y: {
- position: 'right',
- gridLines: {
- display: true,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false,
- }
- }
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
- },
-
- disk(el) {
- if (this.chartDisk != null) return;
- this.chartDisk = markRaw(new Chart(el, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'Read',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#94a029',
- backgroundColor: alpha('#94a029', 0.1),
- data: []
- }, {
- label: 'Write',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#ff9156',
- backgroundColor: alpha('#ff9156', 0.1),
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- x: {
- gridLines: {
- display: false,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false
- }
- },
- y: {
- position: 'right',
- gridLines: {
- display: true,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false,
- }
- }
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
- },
-
- fetchJobs() {
- os.api('admin/queue/deliver-delayed', {}).then(jobs => {
- this.jobs = jobs;
- });
- },
-
- onStats(stats) {
- if (this.paused) return;
-
- const cpu = (stats.cpu * 100).toFixed(0);
- const memActive = (stats.mem.active / this.serverInfo.mem.total * 100).toFixed(0);
- const memUsed = (stats.mem.used / this.serverInfo.mem.total * 100).toFixed(0);
- this.memUsage = stats.mem.active;
-
- this.chartCpuMem.data.labels.push('');
- this.chartCpuMem.data.datasets[0].data.push(cpu);
- this.chartCpuMem.data.datasets[1].data.push(memActive);
- this.chartCpuMem.data.datasets[2].data.push(memUsed);
- this.chartNet.data.labels.push('');
- this.chartNet.data.datasets[0].data.push(stats.net.rx);
- this.chartNet.data.datasets[1].data.push(stats.net.tx);
- this.chartDisk.data.labels.push('');
- this.chartDisk.data.datasets[0].data.push(stats.fs.r);
- this.chartDisk.data.datasets[1].data.push(stats.fs.w);
- if (this.chartCpuMem.data.datasets[0].data.length > 150) {
- this.chartCpuMem.data.labels.shift();
- this.chartCpuMem.data.datasets[0].data.shift();
- this.chartCpuMem.data.datasets[1].data.shift();
- this.chartCpuMem.data.datasets[2].data.shift();
- this.chartNet.data.labels.shift();
- this.chartNet.data.datasets[0].data.shift();
- this.chartNet.data.datasets[1].data.shift();
- this.chartDisk.data.labels.shift();
- this.chartDisk.data.datasets[0].data.shift();
- this.chartDisk.data.datasets[1].data.shift();
- }
- this.chartCpuMem.update();
- this.chartNet.update();
- this.chartDisk.update();
- },
-
- onStatsLog(statsLog) {
- for (const stats of [...statsLog].reverse()) {
- this.onStats(stats);
- }
- },
-
- bytes,
-
- number,
-
- pause() {
- this.paused = true;
- },
-
- resume() {
- this.paused = false;
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.xhexznfu {
- > div:nth-child(2) {
- padding: 16px;
- border-top: solid 0.5px var(--divider);
- }
-}
-</style>
diff --git a/src/client/pages/admin/object-storage.vue b/src/client/pages/admin/object-storage.vue
deleted file mode 100644
index 2d765270e6..0000000000
--- a/src/client/pages/admin/object-storage.vue
+++ /dev/null
@@ -1,155 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch>
-
- <template v-if="useObjectStorage">
- <FormInput v-model="objectStorageBaseUrl">
- <span>{{ $ts.objectStorageBaseUrl }}</span>
- <template #desc>{{ $ts.objectStorageBaseUrlDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStorageBucket">
- <span>{{ $ts.objectStorageBucket }}</span>
- <template #desc>{{ $ts.objectStorageBucketDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStoragePrefix">
- <span>{{ $ts.objectStoragePrefix }}</span>
- <template #desc>{{ $ts.objectStoragePrefixDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStorageEndpoint">
- <span>{{ $ts.objectStorageEndpoint }}</span>
- <template #desc>{{ $ts.objectStorageEndpointDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStorageRegion">
- <span>{{ $ts.objectStorageRegion }}</span>
- <template #desc>{{ $ts.objectStorageRegionDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStorageAccessKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>Access key</span>
- </FormInput>
-
- <FormInput v-model="objectStorageSecretKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>Secret key</span>
- </FormInput>
-
- <FormSwitch v-model="objectStorageUseSSL">
- {{ $ts.objectStorageUseSSL }}
- <template #desc>{{ $ts.objectStorageUseSSLDesc }}</template>
- </FormSwitch>
-
- <FormSwitch v-model="objectStorageUseProxy">
- {{ $ts.objectStorageUseProxy }}
- <template #desc>{{ $ts.objectStorageUseProxyDesc }}</template>
- </FormSwitch>
-
- <FormSwitch v-model="objectStorageSetPublicRead">
- {{ $ts.objectStorageSetPublicRead }}
- </FormSwitch>
-
- <FormSwitch v-model="objectStorageS3ForcePathStyle">
- s3ForcePathStyle
- </FormSwitch>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.objectStorage,
- icon: 'fas fa-cloud',
- bg: 'var(--bg)',
- },
- useObjectStorage: false,
- objectStorageBaseUrl: null,
- objectStorageBucket: null,
- objectStoragePrefix: null,
- objectStorageEndpoint: null,
- objectStorageRegion: null,
- objectStoragePort: null,
- objectStorageAccessKey: null,
- objectStorageSecretKey: null,
- objectStorageUseSSL: false,
- objectStorageUseProxy: false,
- objectStorageSetPublicRead: false,
- objectStorageS3ForcePathStyle: true,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.useObjectStorage = meta.useObjectStorage;
- this.objectStorageBaseUrl = meta.objectStorageBaseUrl;
- this.objectStorageBucket = meta.objectStorageBucket;
- this.objectStoragePrefix = meta.objectStoragePrefix;
- this.objectStorageEndpoint = meta.objectStorageEndpoint;
- this.objectStorageRegion = meta.objectStorageRegion;
- this.objectStoragePort = meta.objectStoragePort;
- this.objectStorageAccessKey = meta.objectStorageAccessKey;
- this.objectStorageSecretKey = meta.objectStorageSecretKey;
- this.objectStorageUseSSL = meta.objectStorageUseSSL;
- this.objectStorageUseProxy = meta.objectStorageUseProxy;
- this.objectStorageSetPublicRead = meta.objectStorageSetPublicRead;
- this.objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- useObjectStorage: this.useObjectStorage,
- objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null,
- objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null,
- objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null,
- objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null,
- objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null,
- objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null,
- objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null,
- objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null,
- objectStorageUseSSL: this.objectStorageUseSSL,
- objectStorageUseProxy: this.objectStorageUseProxy,
- objectStorageSetPublicRead: this.objectStorageSetPublicRead,
- objectStorageS3ForcePathStyle: this.objectStorageS3ForcePathStyle,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/other-settings.vue b/src/client/pages/admin/other-settings.vue
deleted file mode 100644
index 4e55df41fb..0000000000
--- a/src/client/pages/admin/other-settings.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<template>
-<FormBase>
- <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 @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.other,
- icon: 'fas fa-cogs',
- bg: 'var(--bg)',
- },
- 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();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/overview.vue b/src/client/pages/admin/overview.vue
deleted file mode 100644
index ced200351e..0000000000
--- a/src/client/pages/admin/overview.vue
+++ /dev/null
@@ -1,236 +0,0 @@
-<template>
-<div class="edbbcaef" v-size="{ max: [740] }">
- <div v-if="stats" class="cfcdecdf" style="margin: var(--margin)">
- <div class="number _panel">
- <div class="label">Users</div>
- <div class="value _monospace">
- {{ number(stats.originalUsersCount) }}
- <MkNumberDiff v-if="usersComparedToThePrevDay != null" class="diff" :value="usersComparedToThePrevDay" v-tooltip="$ts.dayOverDayChanges"><template #before>(</template><template #after>)</template></MkNumberDiff>
- </div>
- </div>
- <div class="number _panel">
- <div class="label">Notes</div>
- <div class="value _monospace">
- {{ number(stats.originalNotesCount) }}
- <MkNumberDiff v-if="notesComparedToThePrevDay != null" class="diff" :value="notesComparedToThePrevDay" v-tooltip="$ts.dayOverDayChanges"><template #before>(</template><template #after>)</template></MkNumberDiff>
- </div>
- </div>
- </div>
-
- <MkContainer :foldable="true" class="charts">
- <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template>
- <div style="padding-top: 12px;">
- <MkInstanceStats :chart-limit="500" :detailed="true"/>
- </div>
- </MkContainer>
-
- <div class="queue">
- <MkContainer :foldable="true" :thin="true" class="deliver">
- <template #header>Queue: deliver</template>
- <MkQueueChart :connection="queueStatsConnection" domain="deliver"/>
- </MkContainer>
- <MkContainer :foldable="true" :thin="true" class="inbox">
- <template #header>Queue: inbox</template>
- <MkQueueChart :connection="queueStatsConnection" domain="inbox"/>
- </MkContainer>
- </div>
-
- <!--<XMetrics/>-->
-
- <MkFolder style="margin: var(--margin)">
- <template #header><i class="fas fa-info-circle"></i> {{ $ts.info }}</template>
- <div class="cfcdecdf">
- <div class="number _panel">
- <div class="label">Misskey</div>
- <div class="value _monospace">{{ version }}</div>
- </div>
- <div class="number _panel" v-if="serverInfo">
- <div class="label">Node.js</div>
- <div class="value _monospace">{{ serverInfo.node }}</div>
- </div>
- <div class="number _panel" v-if="serverInfo">
- <div class="label">PostgreSQL</div>
- <div class="value _monospace">{{ serverInfo.psql }}</div>
- </div>
- <div class="number _panel" v-if="serverInfo">
- <div class="label">Redis</div>
- <div class="value _monospace">{{ serverInfo.redis }}</div>
- </div>
- <div class="number _panel">
- <div class="label">Vue</div>
- <div class="value _monospace">{{ vueVersion }}</div>
- </div>
- </div>
- </MkFolder>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, markRaw, version as vueVersion } from 'vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import MkInstanceStats from '@client/components/instance-stats.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkNumberDiff from '@client/components/number-diff.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import MkQueueChart from '@client/components/queue-chart.vue';
-import { version, url } from '@client/config';
-import bytes from '@client/filters/bytes';
-import number from '@client/filters/number';
-import MkInstanceInfo from './instance.vue';
-import XMetrics from './metrics.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkNumberDiff,
- FormKeyValueView,
- MkInstanceStats,
- MkContainer,
- MkFolder,
- MkQueueChart,
- XMetrics,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.dashboard,
- icon: 'fas fa-tachometer-alt',
- bg: 'var(--bg)',
- },
- version,
- vueVersion,
- url,
- stats: null,
- meta: null,
- serverInfo: null,
- usersComparedToThePrevDay: null,
- notesComparedToThePrevDay: null,
- fetchJobs: () => os.api('admin/queue/deliver-delayed', {}),
- fetchModLogs: () => os.api('admin/show-moderation-logs', {}),
- queueStatsConnection: markRaw(os.stream.useChannel('queueStats')),
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
- os.api('meta', { detail: true }).then(meta => {
- this.meta = meta;
- });
-
- os.api('stats', {}).then(stats => {
- this.stats = stats;
-
- os.api('charts/users', { limit: 2, span: 'day' }).then(chart => {
- this.usersComparedToThePrevDay = this.stats.originalUsersCount - chart.local.total[1];
- });
-
- os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
- this.notesComparedToThePrevDay = this.stats.originalNotesCount - chart.local.total[1];
- });
- });
-
- os.api('admin/server-info', {}).then(serverInfo => {
- this.serverInfo = serverInfo;
- });
-
- this.$nextTick(() => {
- this.queueStatsConnection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 200
- });
- });
- },
-
- beforeUnmount() {
- this.queueStatsConnection.dispose();
- },
-
- methods: {
- async showInstanceInfo(q) {
- let instance = q;
- if (typeof q === 'string') {
- instance = await os.api('federation/show-instance', {
- host: q
- });
- }
- os.popup(MkInstanceInfo, {
- instance: instance
- }, {}, 'closed');
- },
-
- bytes,
-
- number,
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.edbbcaef {
- .cfcdecdf {
- display: grid;
- grid-gap: 8px;
- grid-template-columns: repeat(auto-fill,minmax(150px,1fr));
-
- > .number {
- padding: 12px 16px;
-
- > .label {
- opacity: 0.7;
- font-size: 0.8em;
- }
-
- > .value {
- font-weight: bold;
- font-size: 1.2em;
-
- > .diff {
- font-size: 0.8em;
- }
- }
- }
- }
-
- > .charts {
- margin: var(--margin);
- }
-
- > .queue {
- margin: var(--margin);
- display: flex;
-
- > .deliver,
- > .inbox {
- flex: 1;
- width: 50%;
-
- &:not(:first-child) {
- margin-left: var(--margin);
- }
- }
- }
-
- &.max-width_740px {
- > .queue {
- display: block;
-
- > .deliver,
- > .inbox {
- width: 100%;
-
- &:not(:first-child) {
- margin-top: var(--margin);
- margin-left: 0;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/admin/proxy-account.vue b/src/client/pages/admin/proxy-account.vue
deleted file mode 100644
index b1ece19710..0000000000
--- a/src/client/pages/admin/proxy-account.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-<template>
-<FormBase>
- <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>
-
- <FormButton @click="chooseProxyAccount" primary>{{ $ts.selectAccount }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormKeyValueView,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.proxyAccount,
- icon: 'fas fa-ghost',
- bg: 'var(--bg)',
- },
- proxyAccount: null,
- proxyAccountId: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.proxyAccountId = meta.proxyAccountId;
- if (this.proxyAccountId) {
- this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId });
- }
- },
-
- chooseProxyAccount() {
- os.selectUser().then(user => {
- this.proxyAccount = user;
- this.proxyAccountId = user.id;
- this.save();
- });
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- proxyAccountId: this.proxyAccountId,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/queue.chart.vue b/src/client/pages/admin/queue.chart.vue
deleted file mode 100644
index 084181a606..0000000000
--- a/src/client/pages/admin/queue.chart.vue
+++ /dev/null
@@ -1,102 +0,0 @@
-<template>
-<div class="_debobigegoItem">
- <div class="_debobigegoLabel"><slot name="title"></slot></div>
- <div class="_debobigegoPanel pumxzjhg">
- <div class="_table status">
- <div class="_row">
- <div class="_cell"><div class="_label">Process</div>{{ number(activeSincePrevTick) }}</div>
- <div class="_cell"><div class="_label">Active</div>{{ number(active) }}</div>
- <div class="_cell"><div class="_label">Waiting</div>{{ number(waiting) }}</div>
- <div class="_cell"><div class="_label">Delayed</div>{{ number(delayed) }}</div>
- </div>
- </div>
- <div class="">
- <MkQueueChart :domain="domain" :connection="connection"/>
- </div>
- <div class="jobs">
- <div v-if="jobs.length > 0">
- <div v-for="job in jobs" :key="job[0]">
- <span>{{ job[0] }}</span>
- <span style="margin-left: 8px; opacity: 0.7;">({{ number(job[1]) }} jobs)</span>
- </div>
- </div>
- <span v-else style="opacity: 0.5;">{{ $ts.noJobs }}</span>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw, onMounted, onUnmounted, ref } from 'vue';
-import number from '@client/filters/number';
-import MkQueueChart from '@client/components/queue-chart.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkQueueChart
- },
-
- props: {
- domain: {
- type: String,
- required: true,
- },
- connection: {
- required: true,
- },
- },
-
- setup(props) {
- const activeSincePrevTick = ref(0);
- const active = ref(0);
- const waiting = ref(0);
- const delayed = ref(0);
- const jobs = ref([]);
-
- onMounted(() => {
- os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(jobs => {
- jobs.value = jobs;
- });
-
- const onStats = (stats) => {
- activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
- active.value = stats[props.domain].active;
- waiting.value = stats[props.domain].waiting;
- delayed.value = stats[props.domain].delayed;
- };
-
- props.connection.on('stats', onStats);
-
- onUnmounted(() => {
- props.connection.off('stats', onStats);
- });
- });
-
- return {
- jobs,
- activeSincePrevTick,
- active,
- waiting,
- delayed,
- number,
- };
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.pumxzjhg {
- > .status {
- padding: 16px;
- border-bottom: solid 0.5px var(--divider);
- }
-
- > .jobs {
- padding: 16px;
- border-top: solid 0.5px var(--divider);
- max-height: 180px;
- overflow: auto;
- }
-}
-</style>
diff --git a/src/client/pages/admin/queue.vue b/src/client/pages/admin/queue.vue
deleted file mode 100644
index f88825eb19..0000000000
--- a/src/client/pages/admin/queue.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<template>
-<FormBase>
- <XQueue :connection="connection" domain="inbox">
- <template #title>In</template>
- </XQueue>
- <XQueue :connection="connection" domain="deliver">
- <template #title>Out</template>
- </XQueue>
- <FormButton @click="clear()" danger><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import XQueue from './queue.chart.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- MkButton,
- XQueue,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.jobQueue,
- icon: 'fas fa-clipboard-list',
- bg: 'var(--bg)',
- },
- connection: markRaw(os.stream.useChannel('queueStats')),
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
- this.$nextTick(() => {
- this.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 200
- });
- });
- },
-
- beforeUnmount() {
- this.connection.dispose();
- },
-
- methods: {
- clear() {
- os.dialog({
- type: 'warning',
- title: this.$ts.clearQueueConfirmTitle,
- text: this.$ts.clearQueueConfirmText,
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
-
- os.apiWithDialog('admin/queue/clear', {});
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/relays.vue b/src/client/pages/admin/relays.vue
deleted file mode 100644
index 7d7888eaa8..0000000000
--- a/src/client/pages/admin/relays.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-<FormBase class="relaycxt">
- <FormButton @click="addRelay" primary><i class="fas fa-plus"></i> {{ $ts.addRelay }}</FormButton>
-
- <div class="_debobigegoItem" v-for="relay in relays" :key="relay.inbox">
- <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>
- </div>
- </div>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- MkButton,
- MkInput,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.relays,
- icon: 'fas fa-globe',
- bg: 'var(--bg)',
- },
- relays: [],
- inbox: '',
- }
- },
-
- created() {
- this.refresh();
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async addRelay() {
- const { canceled, result: inbox } = await os.dialog({
- title: this.$ts.addRelay,
- input: {
- placeholder: this.$ts.inboxUrl
- }
- });
- if (canceled) return;
- os.api('admin/relays/add', {
- inbox
- }).then((relay: any) => {
- this.refresh();
- }).catch((e: any) => {
- os.dialog({
- type: 'error',
- text: e.message || e
- });
- });
- },
-
- remove(inbox: string) {
- os.api('admin/relays/remove', {
- inbox
- }).then(() => {
- this.refresh();
- }).catch((e: any) => {
- os.dialog({
- type: 'error',
- text: e.message || e
- });
- });
- },
-
- refresh() {
- os.api('admin/relays/list').then((relays: any) => {
- this.relays = relays;
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/admin/security.vue b/src/client/pages/admin/security.vue
deleted file mode 100644
index 4365b6800c..0000000000
--- a/src/client/pages/admin/security.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormLink to="/admin/bot-protection">
- <i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}
- <template #suffix v-if="enableHcaptcha">hCaptcha</template>
- <template #suffix v-else-if="enableRecaptcha">reCAPTCHA</template>
- <template #suffix v-else>{{ $ts.none }} ({{ $ts.notRecommended }})</template>
- </FormLink>
-
- <FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch>
-
- <FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormLink,
- FormSwitch,
- FormBase,
- FormGroup,
- FormButton,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.security,
- icon: 'fas fa-lock',
- bg: 'var(--bg)',
- },
- 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.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,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/service-worker.vue b/src/client/pages/admin/service-worker.vue
deleted file mode 100644
index 430e02ad2e..0000000000
--- a/src/client/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 @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/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/src/client/pages/admin/settings.vue b/src/client/pages/admin/settings.vue
deleted file mode 100644
index 7bd363e5f3..0000000000
--- a/src/client/pages/admin/settings.vue
+++ /dev/null
@@ -1,151 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormInput v-model="name">
- <span>{{ $ts.instanceName }}</span>
- </FormInput>
-
- <FormTextarea v-model="description">
- <span>{{ $ts.instanceDescription }}</span>
- </FormTextarea>
-
- <FormInput v-model="iconUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.iconUrl }}</span>
- </FormInput>
-
- <FormInput v-model="bannerUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.bannerUrl }}</span>
- </FormInput>
-
- <FormInput v-model="backgroundImageUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.backgroundImageUrl }}</span>
- </FormInput>
-
- <FormInput v-model="tosUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.tosUrl }}</span>
- </FormInput>
-
- <FormInput v-model="maintainerName">
- <span>{{ $ts.maintainerName }}</span>
- </FormInput>
-
- <FormInput v-model="maintainerEmail" type="email">
- <template #prefix><i class="fas fa-envelope"></i></template>
- <span>{{ $ts.maintainerEmail }}</span>
- </FormInput>
-
- <FormTextarea v-model="pinnedUsers">
- <span>{{ $ts.pinnedUsers }}</span>
- <template #desc>{{ $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>
-
- <FormSwitch v-model="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch>
- <FormSwitch v-model="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch>
- <FormInfo>{{ $ts.disablingTimelinesInfo }}</FormInfo>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.general,
- icon: 'fas fa-cog',
- bg: 'var(--bg)',
- },
- name: null,
- description: null,
- tosUrl: null as string | null,
- maintainerName: null,
- maintainerEmail: null,
- iconUrl: null,
- bannerUrl: null,
- backgroundImageUrl: null,
- maxNoteTextLength: 0,
- enableLocalTimeline: false,
- enableGlobalTimeline: false,
- pinnedUsers: '',
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.name = meta.name;
- this.description = meta.description;
- this.tosUrl = meta.tosUrl;
- this.iconUrl = meta.iconUrl;
- this.bannerUrl = meta.bannerUrl;
- this.backgroundImageUrl = meta.backgroundImageUrl;
- this.maintainerName = meta.maintainerName;
- this.maintainerEmail = meta.maintainerEmail;
- this.maxNoteTextLength = meta.maxNoteTextLength;
- this.enableLocalTimeline = !meta.disableLocalTimeline;
- this.enableGlobalTimeline = !meta.disableGlobalTimeline;
- this.pinnedUsers = meta.pinnedUsers.join('\n');
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- name: this.name,
- description: this.description,
- tosUrl: this.tosUrl,
- iconUrl: this.iconUrl,
- bannerUrl: this.bannerUrl,
- backgroundImageUrl: this.backgroundImageUrl,
- maintainerName: this.maintainerName,
- maintainerEmail: this.maintainerEmail,
- maxNoteTextLength: this.maxNoteTextLength,
- disableLocalTimeline: !this.enableLocalTimeline,
- disableGlobalTimeline: !this.enableGlobalTimeline,
- pinnedUsers: this.pinnedUsers.split('\n'),
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/admin/users.vue b/src/client/pages/admin/users.vue
deleted file mode 100644
index 37a54d2de3..0000000000
--- a/src/client/pages/admin/users.vue
+++ /dev/null
@@ -1,254 +0,0 @@
-<template>
-<div class="lknzcolw">
- <div class="users">
- <div class="inputs">
- <MkSelect v-model="sort" style="flex: 1;">
- <template #label>{{ $ts.sort }}</template>
- <option value="-createdAt">{{ $ts.registeredDate }} ({{ $ts.ascendingOrder }})</option>
- <option value="+createdAt">{{ $ts.registeredDate }} ({{ $ts.descendingOrder }})</option>
- <option value="-updatedAt">{{ $ts.lastUsed }} ({{ $ts.ascendingOrder }})</option>
- <option value="+updatedAt">{{ $ts.lastUsed }} ({{ $ts.descendingOrder }})</option>
- </MkSelect>
- <MkSelect v-model="state" style="flex: 1;">
- <template #label>{{ $ts.state }}</template>
- <option value="all">{{ $ts.all }}</option>
- <option value="available">{{ $ts.normal }}</option>
- <option value="admin">{{ $ts.administrator }}</option>
- <option value="moderator">{{ $ts.moderator }}</option>
- <option value="silenced">{{ $ts.silence }}</option>
- <option value="suspended">{{ $ts.suspend }}</option>
- </MkSelect>
- <MkSelect v-model="origin" style="flex: 1;">
- <template #label>{{ $ts.instance }}</template>
- <option value="combined">{{ $ts.all }}</option>
- <option value="local">{{ $ts.local }}</option>
- <option value="remote">{{ $ts.remote }}</option>
- </MkSelect>
- </div>
- <div class="inputs">
- <MkInput v-model="searchUsername" style="flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()">
- <template #prefix>@</template>
- <template #label>{{ $ts.username }}</template>
- </MkInput>
- <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()" :disabled="pagination.params().origin === 'local'">
- <template #prefix>@</template>
- <template #label>{{ $ts.host }}</template>
- </MkInput>
- </div>
-
- <MkPagination :pagination="pagination" #default="{items}" class="users" ref="users">
- <button class="user _panel _button _gap" v-for="user in items" :key="user.id" @click="show(user)">
- <MkAvatar class="avatar" :user="user" :disable-link="true" :show-indicator="true"/>
- <div class="body">
- <header>
- <MkUserName class="name" :user="user"/>
- <span class="acct">@{{ acct(user) }}</span>
- <span class="staff" v-if="user.isAdmin"><i class="fas fa-bookmark"></i></span>
- <span class="staff" v-if="user.isModerator"><i class="far fa-bookmark"></i></span>
- <span class="punished" v-if="user.isSilenced"><i class="fas fa-microphone-slash"></i></span>
- <span class="punished" v-if="user.isSuspended"><i class="fas fa-snowflake"></i></span>
- </header>
- <div>
- <span>{{ $ts.lastUsed }}: <MkTime v-if="user.updatedAt" :time="user.updatedAt" mode="detail"/></span>
- </div>
- <div>
- <span>{{ $ts.registeredDate }}: <MkTime :time="user.createdAt" mode="detail"/></span>
- </div>
- </div>
- </button>
- </MkPagination>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import { acct } from '@client/filters/user';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { lookupUser } from '@client/scripts/lookup-user';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkPagination,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.users,
- icon: 'fas fa-users',
- bg: 'var(--bg)',
- actions: [{
- icon: 'fas fa-search',
- text: this.$ts.search,
- handler: this.searchUser
- }, {
- asFullButton: true,
- icon: 'fas fa-plus',
- text: this.$ts.addUser,
- handler: this.addUser
- }, {
- asFullButton: true,
- icon: 'fas fa-search',
- text: this.$ts.lookup,
- handler: this.lookupUser
- }],
- },
- sort: '+createdAt',
- state: 'all',
- origin: 'local',
- searchUsername: '',
- searchHost: '',
- pagination: {
- endpoint: 'admin/show-users',
- limit: 10,
- params: () => ({
- 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,
-
- searchUser() {
- os.selectUser().then(user => {
- this.show(user);
- });
- },
-
- async addUser() {
- const { canceled: canceled1, result: username } = await os.dialog({
- title: this.$ts.username,
- input: true
- });
- if (canceled1) return;
-
- const { canceled: canceled2, result: password } = await os.dialog({
- title: this.$ts.password,
- input: { type: 'password' }
- });
- if (canceled2) return;
-
- os.apiWithDialog('admin/accounts/create', {
- username: username,
- password: password,
- }).then(res => {
- this.$refs.users.reload();
- });
- },
-
- show(user) {
- os.pageWindow(`/user-info/${user.id}`);
- },
-
- acct
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.lknzcolw {
- > .users {
- margin: var(--margin);
-
- > .inputs {
- display: flex;
- margin-bottom: 16px;
-
- > * {
- margin-right: 16px;
-
- &:last-child {
- margin-right: 0;
- }
- }
- }
-
- > .users {
- margin-top: var(--margin);
-
- > .user {
- display: flex;
- width: 100%;
- box-sizing: border-box;
- text-align: left;
- align-items: center;
- padding: 16px;
-
- &:hover {
- color: var(--accent);
- }
-
- > .avatar {
- width: 60px;
- height: 60px;
- }
-
- > .body {
- margin-left: 0.3em;
- padding: 0 8px;
- flex: 1;
-
- @media (max-width: 500px) {
- font-size: 14px;
- }
-
- > header {
- > .name {
- font-weight: bold;
- }
-
- > .acct {
- margin-left: 8px;
- opacity: 0.7;
- }
-
- > .staff {
- margin-left: 0.5em;
- color: var(--badge);
- }
-
- > .punished {
- margin-left: 0.5em;
- color: #4dabf7;
- }
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/advanced-theme-editor.vue b/src/client/pages/advanced-theme-editor.vue
deleted file mode 100644
index 8a63d74887..0000000000
--- a/src/client/pages/advanced-theme-editor.vue
+++ /dev/null
@@ -1,352 +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 class="item" v-for="([ k, v ], i) in theme" :key="k">
- <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" v-text="baseProps[k]" class="default-value" />
- <!-- 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 class="select" v-else-if="v.type === 'refProp'" v-model="v.key">
- <option v-for="key in themeProps" :value="key" :key="key">{{ $t('_theme.keys.' + key) }}</option>
- </MkSelect>
- <!-- func -->
- <template v-else-if="v.type === 'func'">
- <MkSelect class="select" v-model="v.name">
- <template #label>{{ $ts._theme.funcKind }}</template>
- <option v-for="n in ['alpha', 'darken', 'lighten']" :value="n" :key="n">{{ $t('_theme.' + n) }}</option>
- </MkSelect>
- <MkInput type="number" v-model="v.arg"><span>{{ $ts._theme.argument }}</span></MkInput>
- <MkSelect class="select" v-model="v.value">
- <template #label>{{ $ts._theme.basedProp }}</template>
- <option v-for="key in themeProps" :value="key" :key="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 '@client/components/form/radio.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkSample from '@client/components/sample.vue';
-
-import { convertToMisskeyTheme, ThemeValue, convertToViewModel, ThemeViewModel } from '@client/scripts/theme-editor';
-import { Theme, applyTheme, lightTheme, darkTheme, themeProps, validateTheme } from '@client/scripts/theme';
-import { host } from '@client/config';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import { addTheme } from '@client/theme-store';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkRadio,
- MkButton,
- MkInput,
- MkTextarea,
- MkSelect,
- MkSample,
- },
-
- 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);
- },
-
- async beforeRouteLeave(to, from, next) {
- if (this.changed && !(await this.confirm())) {
- next(false);
- } else {
- next();
- }
- },
-
- 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.dialog({
- type: 'warning',
- text: this.$ts.leaveConfirm,
- showCancelButton: true
- });
- 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.dialog({
- type: 'warning',
- showCancelButton: true,
- 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.dialog({
- title: this.$ts._theme.inputConstantName,
- input: true
- });
- 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.dialog({
- 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.dialog({
- 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.dialog({
- 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/src/client/pages/announcements.vue b/src/client/pages/announcements.vue
deleted file mode 100644
index 429d183d1e..0000000000
--- a/src/client/pages/announcements.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<template>
-<MkSpacer :content-max="800">
- <MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content">
- <section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id">
- <div class="_title"><span v-if="$i && !announcement.isRead">๐Ÿ†• </span>{{ announcement.title }}</div>
- <div class="_content">
- <Mfm :text="announcement.text"/>
- <img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
- </div>
- <div class="_footer" v-if="$i && !announcement.isRead">
- <MkButton @click="read(items, announcement, i)" primary><i class="fas fa-check"></i> {{ $ts.gotIt }}</MkButton>
- </div>
- </section>
- </MkPagination>
-</MkSpacer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkButton
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.announcements,
- icon: 'fas fa-broadcast-tower',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'announcements',
- limit: 10,
- },
- };
- },
-
- methods: {
- // TODO: ใ“ใ‚ŒใฏๅฎŸ่ณช็š„ใซ่ฆชใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ‹ใ‚‰ๅญใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใฎใƒ—ใƒญใƒ‘ใƒ†ใ‚ฃใ‚’ๅค‰ๆ›ดใ—ใฆใ‚‹ใฎใงใชใ‚“ใจใ‹ใ—ใŸใ„
- read(items, announcement, i) {
- items[i] = {
- ...announcement,
- isRead: true,
- };
- os.api('i/read-announcement', { announcementId: announcement.id });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.ruryvtyk {
- > .announcement {
- &:not(:last-child) {
- margin-bottom: var(--margin);
- }
-
- > ._content {
- > img {
- display: block;
- max-height: 300px;
- max-width: 100%;
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/antenna-timeline.vue b/src/client/pages/antenna-timeline.vue
deleted file mode 100644
index c99124dbdc..0000000000
--- a/src/client/pages/antenna-timeline.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<template>
-<div class="tqmomfks" v-hotkey.global="keymap" v-size="{ min: [800] }">
- <div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
- <div class="tl _block">
- <XTimeline ref="tl" class="tl"
- :key="antennaId"
- src="antenna"
- :antenna="antennaId"
- :sound="true"
- @before="before()"
- @after="after()"
- @queue="queueUpdated"
- />
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import Progress from '@client/scripts/loading';
-import XTimeline from '@client/components/timeline.vue';
-import { scroll } from '@client/scripts/scroll';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XTimeline,
- },
-
- props: {
- antennaId: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- antenna: null,
- queue: 0,
- [symbols.PAGE_INFO]: computed(() => this.antenna ? {
- title: this.antenna.name,
- icon: 'fas fa-satellite',
- bg: 'var(--bg)',
- actions: [{
- icon: 'fas fa-calendar-alt',
- text: this.$ts.jumpToSpecifiedDate,
- handler: this.timetravel
- }, {
- icon: 'fas fa-cog',
- text: this.$ts.settings,
- handler: this.settings
- }],
- } : null),
- };
- },
-
- computed: {
- keymap(): any {
- return {
- 't': this.focus
- };
- },
- },
-
- watch: {
- antennaId: {
- async handler() {
- this.antenna = await os.api('antennas/show', {
- antennaId: this.antennaId
- });
- },
- immediate: true
- }
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- },
-
- queueUpdated(q) {
- this.queue = q;
- },
-
- top() {
- scroll(this.$el, { top: 0 });
- },
-
- async timetravel() {
- const { canceled, result: date } = await os.dialog({
- title: this.$ts.date,
- input: {
- type: 'date'
- }
- });
- if (canceled) return;
-
- this.$refs.tl.timetravel(new Date(date));
- },
-
- settings() {
- this.$router.push(`/my/antennas/${this.antennaId}`);
- },
-
- focus() {
- (this.$refs.tl as any).focus();
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.tqmomfks {
- padding: var(--margin);
-
- > .new {
- position: sticky;
- top: calc(var(--stickyTop, 0px) + 16px);
- z-index: 1000;
- width: 100%;
-
- > button {
- display: block;
- margin: var(--margin) auto 0 auto;
- padding: 8px 16px;
- border-radius: 32px;
- }
- }
-
- > .tl {
- background: var(--bg);
- border-radius: var(--radius);
- overflow: clip;
- }
-
- &.min-width_800px {
- max-width: 800px;
- margin: 0 auto;
- }
-}
-</style>
diff --git a/src/client/pages/api-console.vue b/src/client/pages/api-console.vue
deleted file mode 100644
index 9aa7d4ea4d..0000000000
--- a/src/client/pages/api-console.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-<template>
-<div class="_root">
- <div class="_block" style="padding: 24px;">
- <MkInput v-model="endpoint" :datalist="endpoints" @update:modelValue="onEndpointChange()" class="">
- <template #label>Endpoint</template>
- </MkInput>
- <MkTextarea v-model="body" code>
- <template #label>Params (JSON or JSON5)</template>
- </MkTextarea>
- <MkSwitch v-model="withCredential">
- With credential
- </MkSwitch>
- <MkButton primary full @click="send" :disabled="sending">
- <template v-if="sending"><MkEllipsis/></template>
- <template v-else><i class="fas fa-paper-plane"></i> Send</template>
- </MkButton>
- </div>
- <div v-if="res" class="_block" style="padding: 24px;">
- <MkTextarea v-model="res" code readonly tall>
- <template #label>Response</template>
- </MkTextarea>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton, MkInput, MkTextarea, MkSwitch,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'API console',
- icon: 'fas fa-terminal'
- },
-
- endpoint: '',
- body: '{}',
- res: null,
- sending: false,
- endpoints: [],
- withCredential: true,
-
- };
- },
-
- created() {
- os.api('endpoints').then(endpoints => {
- this.endpoints = endpoints;
- });
- },
-
- methods: {
- send() {
- this.sending = true;
- os.api(this.endpoint, JSON5.parse(this.body)).then(res => {
- this.sending = false;
- this.res = JSON5.stringify(res, null, 2);
- }, err => {
- this.sending = false;
- this.res = JSON5.stringify(err, null, 2);
- });
- },
-
- onEndpointChange() {
- os.api('endpoint', { endpoint: this.endpoint }, this.withCredential ? undefined : null).then(endpoint => {
- const body = {};
- for (const p of endpoint.params) {
- body[p.name] =
- p.type === 'String' ? '' :
- p.type === 'Number' ? 0 :
- p.type === 'Boolean' ? false :
- p.type === 'Array' ? [] :
- p.type === 'Object' ? {} :
- null;
- }
- this.body = JSON5.stringify(body, null, 2);
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/auth.form.vue b/src/client/pages/auth.form.vue
deleted file mode 100644
index 10c466c73c..0000000000
--- a/src/client/pages/auth.form.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-<template>
-<section class="_section">
- <div class="_title">{{ $t('_auth.shareAccess', { name: app.name }) }}</div>
- <div class="_content">
- <h2>{{ app.name }}</h2>
- <p class="id">{{ app.id }}</p>
- <p class="description">{{ app.description }}</p>
- </div>
- <div class="_content">
- <h2>{{ $ts._auth.permissionAsk }}</h2>
- <ul>
- <li v-for="p in app.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
- </ul>
- </div>
- <div class="_footer">
- <MkButton @click="cancel" inline>{{ $ts.cancel }}</MkButton>
- <MkButton @click="accept" inline primary>{{ $ts.accept }}</MkButton>
- </div>
-</section>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkButton
- },
- props: ['session'],
- computed: {
- name(): string {
- const el = document.createElement('div');
- el.textContent = this.app.name
- return el.innerHTML;
- },
- app(): any {
- return this.session.app;
- }
- },
- methods: {
- cancel() {
- os.api('auth/deny', {
- token: this.session.token
- }).then(() => {
- this.$emit('denied');
- });
- },
-
- accept() {
- os.api('auth/accept', {
- token: this.session.token
- }).then(() => {
- this.$emit('accepted');
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/auth.vue b/src/client/pages/auth.vue
deleted file mode 100755
index 3656d48c42..0000000000
--- a/src/client/pages/auth.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-<template>
-<div class="" v-if="$i && fetching">
- <MkLoading/>
-</div>
-<div v-else-if="$i">
- <XForm
- class="form"
- ref="form"
- v-if="state == 'waiting'"
- :session="session"
- @denied="state = 'denied'"
- @accepted="accepted"
- />
- <div class="denied" v-if="state == 'denied'">
- <h1>{{ $ts._auth.denied }}</h1>
- </div>
- <div class="accepted" v-if="state == 'accepted'">
- <h1>{{ session.app.isAuthorized ? this.$t('already-authorized') : this.$ts.allowed }}</h1>
- <p v-if="session.app.callbackUrl">{{ $ts._auth.callback }}<MkEllipsis/></p>
- <p v-if="!session.app.callbackUrl">{{ $ts._auth.pleaseGoBack }}</p>
- </div>
- <div class="error" v-if="state == 'fetch-session-error'">
- <p>{{ $ts.somethingHappened }}</p>
- </div>
-</div>
-<div class="signin" v-else>
- <MkSignin @login="onLogin"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XForm from './auth.form.vue';
-import MkSignin from '@client/components/signin.vue';
-import * as os from '@client/os';
-import { login } from '@client/account';
-
-export default defineComponent({
- components: {
- XForm,
- MkSignin,
- },
- data() {
- return {
- state: null,
- session: null,
- fetching: true
- };
- },
- computed: {
- token(): string {
- return this.$route.params.token;
- }
- },
- mounted() {
- if (!this.$i) return;
-
- // Fetch session
- os.api('auth/session/show', {
- token: this.token
- }).then(session => {
- this.session = session;
- this.fetching = false;
-
- // ๆ—ขใซ้€ฃๆบใ—ใฆใ„ใŸๅ ดๅˆ
- if (this.session.app.isAuthorized) {
- os.api('auth/accept', {
- token: this.session.token
- }).then(() => {
- this.accepted();
- });
- } else {
- this.state = 'waiting';
- }
- }).catch(error => {
- this.state = 'fetch-session-error';
- this.fetching = false;
- });
- },
- methods: {
- accepted() {
- this.state = 'accepted';
- if (this.session.app.callbackUrl) {
- location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`;
- }
- }, onLogin(res) {
- login(res.i);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/channel-editor.vue b/src/client/pages/channel-editor.vue
deleted file mode 100644
index 67e27896ce..0000000000
--- a/src/client/pages/channel-editor.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-<template>
-<div>
- <div class="_section">
- <div class="_content">
- <MkInput v-model="name">
- <template #label>{{ $ts.name }}</template>
- </MkInput>
-
- <MkTextarea v-model="description">
- <template #label>{{ $ts.description }}</template>
- </MkTextarea>
-
- <div class="banner">
- <MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton>
- <div v-else-if="bannerUrl">
- <img :src="bannerUrl" style="width: 100%;"/>
- <MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton>
- </div>
- </div>
- </div>
- <div class="_footer">
- <MkButton @click="save()" primary><i class="fas fa-save"></i> {{ channelId ? $ts.save : $ts.create }}</MkButton>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import { selectFile } from '@client/scripts/select-file';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkTextarea, MkButton, MkInput,
- },
-
- props: {
- channelId: {
- type: String,
- required: false
- },
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.channelId ? {
- title: this.$ts._channel.edit,
- icon: 'fas fa-satellite-dish',
- } : {
- title: this.$ts._channel.create,
- icon: 'fas fa-satellite-dish',
- }),
- channel: null,
- name: null,
- description: null,
- bannerUrl: null,
- bannerId: null,
- };
- },
-
- watch: {
- async bannerId() {
- if (this.bannerId == null) {
- this.bannerUrl = null;
- } else {
- this.bannerUrl = (await os.api('drive/files/show', {
- fileId: this.bannerId,
- })).url;
- }
- },
- },
-
- async created() {
- if (this.channelId) {
- this.channel = await os.api('channels/show', {
- channelId: this.channelId,
- });
-
- this.name = this.channel.name;
- this.description = this.channel.description;
- this.bannerId = this.channel.bannerId;
- this.bannerUrl = this.channel.bannerUrl;
- }
- },
-
- methods: {
- save() {
- const params = {
- name: this.name,
- description: this.description,
- bannerId: this.bannerId,
- };
-
- if (this.channelId) {
- params.channelId = this.channelId;
- os.api('channels/update', params)
- .then(channel => {
- os.success();
- });
- } else {
- os.api('channels/create', params)
- .then(channel => {
- os.success();
- this.$router.push(`/channels/${channel.id}`);
- });
- }
- },
-
- setBannerImage(e) {
- selectFile(e.currentTarget || e.target, null, false).then(file => {
- this.bannerId = file.id;
- });
- },
-
- removeBannerImage() {
- this.bannerId = null;
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/channel.vue b/src/client/pages/channel.vue
deleted file mode 100644
index d725db9e49..0000000000
--- a/src/client/pages/channel.vue
+++ /dev/null
@@ -1,186 +0,0 @@
-<template>
-<div v-if="channel" class="_section">
- <div class="wpgynlbz _content _panel _gap" :class="{ hide: !showBanner }">
- <XChannelFollowButton :channel="channel" :full="true" class="subscribe"/>
- <button class="_button toggle" @click="() => showBanner = !showBanner">
- <template v-if="showBanner"><i class="fas fa-angle-up"></i></template>
- <template v-else><i class="fas fa-angle-down"></i></template>
- </button>
- <div class="hideOverlay" v-if="!showBanner">
- </div>
- <div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner">
- <div class="status">
- <div><i class="fas fa-users fa-fw"></i><I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
- <div><i class="fas fa-pencil-alt fa-fw"></i><I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
- </div>
- <div class="fade"></div>
- </div>
- <div class="description" v-if="channel.description">
- <Mfm :text="channel.description" :is-note="false" :i="$i"/>
- </div>
- </div>
-
- <XPostForm :channel="channel" class="post-form _content _panel _gap" fixed v-if="$i"/>
-
- <XTimeline class="_content _gap" src="channel" :key="channelId" :channel="channelId" @before="before" @after="after"/>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import XPostForm from '@client/components/post-form.vue';
-import XTimeline from '@client/components/timeline.vue';
-import XChannelFollowButton from '@client/components/channel-follow-button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkContainer,
- XPostForm,
- XTimeline,
- XChannelFollowButton
- },
-
- props: {
- channelId: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.channel ? {
- title: this.channel.name,
- icon: 'fas fa-satellite-dish',
- } : null),
- channel: null,
- showBanner: true,
- pagination: {
- endpoint: 'channels/timeline',
- limit: 10,
- params: () => ({
- channelId: this.channelId,
- })
- },
- };
- },
-
- watch: {
- channelId: {
- async handler() {
- this.channel = await os.api('channels/show', {
- channelId: this.channelId,
- });
- },
- immediate: true
- }
- },
-
- created() {
-
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.wpgynlbz {
- position: relative;
-
- > .subscribe {
- position: absolute;
- z-index: 1;
- top: 16px;
- left: 16px;
- }
-
- > .toggle {
- position: absolute;
- z-index: 2;
- top: 8px;
- right: 8px;
- font-size: 1.2em;
- width: 48px;
- height: 48px;
- color: #fff;
- background: rgba(0, 0, 0, 0.5);
- border-radius: 100%;
-
- > i {
- vertical-align: middle;
- }
- }
-
- > .banner {
- position: relative;
- height: 200px;
- background-position: center;
- background-size: cover;
-
- > .fade {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 64px;
- background: linear-gradient(0deg, var(--panel), var(--X15));
- }
-
- > .status {
- position: absolute;
- z-index: 1;
- bottom: 16px;
- right: 16px;
- padding: 8px 12px;
- font-size: 80%;
- background: rgba(0, 0, 0, 0.7);
- border-radius: 6px;
- color: #fff;
- }
- }
-
- > .description {
- padding: 16px;
- }
-
- > .hideOverlay {
- position: absolute;
- z-index: 1;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- -webkit-backdrop-filter: var(--blur, blur(16px));
- backdrop-filter: var(--blur, blur(16px));
- background: rgba(0, 0, 0, 0.3);
- }
-
- &.hide {
- > .subscribe {
- display: none;
- }
-
- > .toggle {
- top: 0;
- right: 0;
- height: 100%;
- background: transparent;
- }
-
- > .banner {
- height: 42px;
- filter: blur(8px);
-
- > * {
- display: none;
- }
- }
-
- > .description {
- display: none;
- }
- }
-}
-</style>
diff --git a/src/client/pages/channels.vue b/src/client/pages/channels.vue
deleted file mode 100644
index fd1408c253..0000000000
--- a/src/client/pages/channels.vue
+++ /dev/null
@@ -1,77 +0,0 @@
-<template>
-<div>
- <div class="_section" style="padding: 0;" v-if="$i">
- <MkTab class="_content" v-model="tab">
- <option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._channel.featured }}</option>
- <option value="following"><i class="fas fa-heart"></i> {{ $ts._channel.following }}</option>
- <option value="owned"><i class="fas fa-edit"></i> {{ $ts._channel.owned }}</option>
- </MkTab>
- </div>
-
- <div class="_section">
- <div class="_content grwlizim featured" v-if="tab === 'featured'">
- <MkPagination :pagination="featuredPagination" #default="{items}">
- <MkChannelPreview v-for="channel in items" class="_gap" :channel="channel" :key="channel.id"/>
- </MkPagination>
- </div>
-
- <div class="_content grwlizim following" v-if="tab === 'following'">
- <MkPagination :pagination="followingPagination" #default="{items}">
- <MkChannelPreview v-for="channel in items" class="_gap" :channel="channel" :key="channel.id"/>
- </MkPagination>
- </div>
-
- <div class="_content grwlizim owned" v-if="tab === 'owned'">
- <MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
- <MkPagination :pagination="ownedPagination" #default="{items}">
- <MkChannelPreview v-for="channel in items" class="_gap" :channel="channel" :key="channel.id"/>
- </MkPagination>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkChannelPreview from '@client/components/channel-preview.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkTab from '@client/components/tab.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkChannelPreview, MkPagination, MkButton, MkTab
- },
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.channel,
- icon: 'fas fa-satellite-dish',
- action: {
- icon: 'fas fa-plus',
- handler: this.create
- }
- },
- tab: 'featured',
- featuredPagination: {
- endpoint: 'channels/featured',
- noPaging: true,
- },
- followingPagination: {
- endpoint: 'channels/followed',
- limit: 5,
- },
- ownedPagination: {
- endpoint: 'channels/owned',
- limit: 5,
- },
- };
- },
- methods: {
- create() {
- this.$router.push(`/channels/new`);
- }
- }
-});
-</script>
diff --git a/src/client/pages/clip.vue b/src/client/pages/clip.vue
deleted file mode 100644
index e4b00d5e28..0000000000
--- a/src/client/pages/clip.vue
+++ /dev/null
@@ -1,154 +0,0 @@
-<template>
-<div v-if="clip" class="_section">
- <div class="okzinsic _content _panel _gap">
- <div class="description" v-if="clip.description">
- <Mfm :text="clip.description" :is-note="false" :i="$i"/>
- </div>
- <div class="user">
- <MkAvatar :user="clip.user" class="avatar" :show-indicator="true"/> <MkUserName :user="clip.user" :nowrap="false"/>
- </div>
- </div>
-
- <XNotes class="_content _gap" :pagination="pagination" :detail="true"/>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import MkContainer from '@client/components/ui/container.vue';
-import XPostForm from '@client/components/post-form.vue';
-import XNotes from '@client/components/notes.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkContainer,
- XPostForm,
- XNotes,
- },
-
- props: {
- clipId: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.clip ? {
- title: this.clip.name,
- icon: 'fas fa-paperclip',
- action: {
- icon: 'fas fa-ellipsis-h',
- handler: this.menu
- }
- } : null),
- clip: null,
- pagination: {
- endpoint: 'clips/notes',
- limit: 10,
- params: () => ({
- clipId: this.clipId,
- })
- },
- };
- },
-
- computed: {
- isOwned(): boolean {
- return this.$i && this.clip && (this.$i.id === this.clip.userId);
- }
- },
-
- watch: {
- clipId: {
- async handler() {
- this.clip = await os.api('clips/show', {
- clipId: this.clipId,
- });
- },
- immediate: true
- }
- },
-
- created() {
-
- },
-
- methods: {
- menu(ev) {
- os.popupMenu([this.isOwned ? {
- icon: 'fas fa-pencil-alt',
- text: this.$ts.edit,
- action: async () => {
- const { canceled, result } = await os.form(this.clip.name, {
- name: {
- type: 'string',
- label: this.$ts.name,
- default: this.clip.name
- },
- description: {
- type: 'string',
- required: false,
- multiline: true,
- label: this.$ts.description,
- default: this.clip.description
- },
- isPublic: {
- type: 'boolean',
- label: this.$ts.public,
- default: this.clip.isPublic
- }
- });
- if (canceled) return;
-
- os.apiWithDialog('clips/update', {
- clipId: this.clip.id,
- ...result
- });
- }
- } : undefined, this.isOwned ? {
- icon: 'fas fa-trash-alt',
- text: this.$ts.delete,
- danger: true,
- action: async () => {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$t('deleteAreYouSure', { x: this.clip.name }),
- showCancelButton: true
- });
- if (canceled) return;
-
- await os.apiWithDialog('clips/delete', {
- clipId: this.clip.id,
- });
- }
- } : undefined], ev.currentTarget || ev.target);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.okzinsic {
- position: relative;
-
- > .description {
- padding: 16px;
- }
-
- > .user {
- $height: 32px;
- padding: 16px;
- border-top: solid 0.5px var(--divider);
- line-height: $height;
-
- > .avatar {
- width: $height;
- height: $height;
- }
- }
-}
-</style>
diff --git a/src/client/pages/doc.vue b/src/client/pages/doc.vue
deleted file mode 100644
index 500d0340b7..0000000000
--- a/src/client/pages/doc.vue
+++ /dev/null
@@ -1,240 +0,0 @@
-<template>
-<div class="qyqbqfal" v-size="{ max: [500] }">
- <div class="main">
- <div class="title">{{ title }}</div>
- <div class="body" v-html="body"></div>
- <div class="footer">
- <MkLink :url="`https://github.com/misskey-dev/misskey/blob/master/src/docs/${lang}/${doc}.md`" class="at">{{ $ts.docSource }}</MkLink>
- <p v-if="lang !== 'ja-JP'">{{ $ts.translateWarn }}</p>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import MarkdownIt from 'markdown-it';
-import MarkdownItAnchor from 'markdown-it-anchor';
-import { url, lang } from '@client/config';
-import MkLink from '@client/components/link.vue';
-import * as symbols from '@client/symbols';
-
-const markdown = MarkdownIt({
- html: true
-});
-
-markdown.use(MarkdownItAnchor, {
- slugify: (s) => encodeURIComponent(String(s).trim().replace(/\s+/g, '-'))
-});
-
-export default defineComponent({
- components: {
- MkLink
- },
-
- props: {
- doc: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.title ? {
- title: this.title,
- icon: 'fas fa-question-circle',
- } : null),
- title: null,
- body: null,
- markdown: null,
- lang,
- }
- },
-
- watch: {
- doc: {
- handler() {
- this.fetchDoc();
- },
- immediate: true,
- }
- },
-
- methods: {
- fetchDoc() {
- fetch(`${url}/doc-assets/${lang}/${this.doc}.md`).then(res => res.text()).then(md => {
- this.parse(md);
- }).catch(() => {
- fetch(`${url}/doc-assets/ja-JP/${this.doc}.md`).then(res => res.text()).then(md => {
- this.parse(md);
- });
- });
- },
-
- parse(md: string) {
- // ๅค‰ๆ•ฐ็ฝฎๆ›
- md = md.replace(/\{_URL_\}/g, url);
-
- // markdown ใฎๅ…จๅฎนใ‚’ใƒ‘ใƒผใ‚นใ™ใ‚‹
- const parsed = markdown.parse(md, {});
- if (parsed.length === 0) return;
-
- const buf = [...parsed];
- const headingTokens = [];
- let headingStart = 0;
-
- // ใ‚‚ใฃใจใ‚‚ไธŠใซใ‚ใ‚‹่ฆ‹ๅ‡บใ—ใ‚’ๆŠฝๅ‡บใ™ใ‚‹
- while (buf[0].type !== 'heading_open') {
- buf.shift();
- headingStart++;
- }
- buf.shift();
- while (buf[0].type as string !== 'heading_close') {
- const token = buf.shift();
- if (token) {
- headingTokens.push(token);
- }
- }
-
- // ๆŠฝๅ‡บใ—ใŸ่ฆ‹ๅ‡บใ—ใ‚’้™คใ้ƒจๅˆ†ใ‚’bodyใจใ—ใฆๆŠฝๅ‡บใ™ใ‚‹
- const bodyTokens = [...parsed];
- bodyTokens.splice(headingStart, headingTokens.length + 2);
-
- // ๅ„ใ€…ใƒฌใƒณใƒ€ใƒผใ™ใ‚‹
- this.title = markdown.renderer.render(headingTokens, {}, {});
- this.body = markdown.renderer.render(bodyTokens, {}, {});
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.qyqbqfal {
- padding: 32px;
- background: var(--panel);
- line-height: 1.5;
-
- &.max-width_500px {
- padding: 24px;
- }
-
- > .main {
- max-width: 800px;
- margin: 0 auto;
-
- > .title {
- font-size: 1.5em;
- font-weight: bold;
- padding: 0 0 0.75em 0;
- margin: 0 0 1em 0;
- border-bottom: solid 2px var(--divider);
- }
-
- > .body {
- > *:first-child {
- margin-top: 0;
- }
-
- > *:last-child {
- margin-bottom: 0;
- }
-
- ::v-deep(a) {
- color: var(--link);
- }
-
- ::v-deep(blockquote) {
- display: block;
- margin: 8px;
- padding: 6px 0 6px 12px;
- color: var(--fg);
- border-left: solid 3px var(--fg);
- opacity: 0.7;
-
- p {
- margin: 0;
- }
- }
-
- ::v-deep(h2) {
- font-size: 1.25em;
- padding: 0 0 0.5em 0;
- margin: 1.5em 0 1em 0;
- border-bottom: solid 0.5px var(--divider);
- }
-
- ::v-deep(h3) {
- margin: 1.25em 0 0.5em 0;
- }
-
- ::v-deep(table) {
- width: 100%;
- max-width: 100%;
- overflow: auto;
- }
-
- ::v-deep(kbd.group) {
- display: inline-block;
- padding: 2px;
- border: 1px solid var(--divider);
- border-radius: 4px;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
- }
-
- ::v-deep(kbd.key) {
- display: inline-block;
- padding: 6px 8px;
- border: solid 0.5px var(--divider);
- border-radius: 4px;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
- }
-
- ::v-deep(code) {
- display: inline-block;
- font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
- tab-size: 2;
- background: #272822;
- color: #f8f8f2;
- border-radius: 6px;
- padding: 4px 6px;
- }
-
- ::v-deep(pre) {
- background: #272822;
- color: #f8f8f2;
- border-radius: 6px;
- padding: 12px 16px;
-
- > code {
- padding: 0;
- }
- }
-
- ::v-deep(.info) {
- font-size: 90%;
- background: var(--infoBg);
- color: var(--infoFg);
- padding: 1em;
- margin: 0.75em 0;
- border-radius: 6px;
- }
-
- ::v-deep(.warn) {
- font-size: 90%;
- background: var(--infoWarnBg);
- color: var(--infoWarnFg);
- padding: 1em;
- margin: 0.75em 0;
- border-radius: 6px;
- }
- }
-
- > .footer {
- padding: 1.5em 0 0 0;
- margin: 1.5em 0 0 0;
- border-top: solid 2px var(--divider);
- }
- }
-}
-</style>
diff --git a/src/client/pages/docs.vue b/src/client/pages/docs.vue
deleted file mode 100644
index 629dc2be53..0000000000
--- a/src/client/pages/docs.vue
+++ /dev/null
@@ -1,152 +0,0 @@
-<template>
-<div class="vtaihdtm">
- <div class="body">
- <div class="search">
- <MkInput v-model="query" :debounce="true" type="search" class="" :placeholder="$ts.search">
- <template #prefix><i class="fas fa-search"></i></template>
- </MkInput>
- </div>
- <div class="list">
- <MkFolder>
- <template #header>{{ $ts._docs.generalTopics }}</template>
- <div class="docs">
- <MkA v-for="doc in docs.filter(doc => doc.path.startsWith('general/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc">
- <div class="title">{{ doc.title }}</div>
- <div class="summary">{{ doc.summary }}</div>
- <div class="read">{{ $ts._docs.continueReading }}</div>
- </MkA>
- </div>
- </MkFolder>
- <MkFolder>
- <template #header>{{ $ts._docs.features }}</template>
- <div class="docs">
- <MkA v-for="doc in docs.filter(doc => doc.path.startsWith('features/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc">
- <div class="title">{{ doc.title }}</div>
- <div class="summary">{{ doc.summary }}</div>
- <div class="read">{{ $ts._docs.continueReading }}</div>
- </MkA>
- </div>
- </MkFolder>
- <MkFolder>
- <template #header>{{ $ts._docs.advancedTopics }}</template>
- <div class="docs">
- <MkA v-for="doc in docs.filter(doc => doc.path.startsWith('advanced/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc">
- <div class="title">{{ doc.title }}</div>
- <div class="summary">{{ doc.summary }}</div>
- <div class="read">{{ $ts._docs.continueReading }}</div>
- </MkA>
- </div>
- </MkFolder>
- <MkFolder>
- <template #header>{{ $ts._docs.admin }}</template>
- <div class="docs">
- <MkA v-for="doc in docs.filter(doc => doc.path.startsWith('admin/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc">
- <div class="title">{{ doc.title }}</div>
- <div class="summary">{{ doc.summary }}</div>
- <div class="read">{{ $ts._docs.continueReading }}</div>
- </MkA>
- </div>
- </MkFolder>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { url, lang } from '@client/config';
-import * as symbols from '@client/symbols';
-import MkFolder from '@client/components/ui/folder.vue';
-import MkInput from '@client/components/form/input.vue';
-
-export default defineComponent({
- components: {
- MkFolder,
- MkInput,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.help,
- icon: 'fas fa-question-circle'
- },
- docs: [],
- query: null,
- }
- },
-
- watch: {
- query() {
- fetch(`${url}/docs.json?lang=${lang}&q=${this.query}`).then(res => res.json()).then(docs => {
- this.docs = docs;
- });
- }
- },
-
- created() {
- fetch(`${url}/docs.json?lang=ja-JP`).then(res => res.json()).then(jaDocs => {
- fetch(`${url}/docs.json?lang=${lang}`).then(res => res.json()).then(docs => {
- this.docs = jaDocs.map(doc => {
- const exist = docs.find(d => d.path === doc.path);
- return exist || doc;
- });
- });
- });
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.vtaihdtm {
- background: var(--panel);
-
- > .body {
- max-width: 900px;
- margin: 0 auto;
-
- > .search {
- padding: 16px;
- }
-
- > .list {
- padding: 0 16px;
-
- .docs {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
- grid-gap: 12px;
- margin: 0 0 16px 0;
-
- > .doc {
- display: inline-block;
- padding: 16px;
- border: solid 1px var(--divider);
- border-radius: 6px;
-
- &:hover {
- border: solid 1px var(--accent);
- text-decoration: none;
- }
-
- > .title {
- font-weight: bold;
- }
-
- > .summary {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- font-size: 0.9em;
- }
-
- > .read {
- color: var(--link);
- font-size: 0.9em;
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/drive.vue b/src/client/pages/drive.vue
deleted file mode 100644
index 9ee1ea8859..0000000000
--- a/src/client/pages/drive.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-<template>
-<div>
- <XDrive ref="drive" @cd="x => folder = x"/>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import XDrive from '@client/components/drive.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XDrive
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: computed(() => this.folder ? this.folder.name : this.$ts.drive),
- icon: 'fas fa-cloud',
- },
- folder: null,
- };
- },
-});
-</script>
diff --git a/src/client/pages/emojis.category.vue b/src/client/pages/emojis.category.vue
deleted file mode 100644
index e725bcb31f..0000000000
--- a/src/client/pages/emojis.category.vue
+++ /dev/null
@@ -1,135 +0,0 @@
-<template>
-<div class="driuhtrh">
- <div class="query">
- <MkInput v-model="q" class="" :placeholder="$ts.search">
- <template #prefix><i class="fas fa-search"></i></template>
- </MkInput>
-
- <!-- ใŸใใ•ใ‚“ใ‚ใ‚‹ใจ้‚ช้ญ”
- <div class="tags">
- <span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
- </div>
- -->
- </div>
-
- <MkFolder class="emojis" v-if="searchEmojis">
- <template #header>{{ $ts.searchResult }}</template>
- <div class="zuvgdzyt">
- <XEmoji v-for="emoji in searchEmojis" :key="emoji.name" class="emoji" :emoji="emoji"/>
- </div>
- </MkFolder>
-
- <MkFolder class="emojis" v-for="category in customEmojiCategories" :key="category">
- <template #header>{{ category || $ts.other }}</template>
- <div class="zuvgdzyt">
- <XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
- </div>
- </MkFolder>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, computed } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import MkTab from '@client/components/tab.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { emojiCategories, emojiTags } from '@client/instance';
-import XEmoji from './emojis.emoji.vue';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkFolder,
- MkTab,
- XEmoji,
- },
-
- data() {
- return {
- q: '',
- customEmojiCategories: emojiCategories,
- customEmojis: this.$instance.emojis,
- tags: emojiTags,
- selectedTags: new Set(),
- searchEmojis: null,
- }
- },
-
- watch: {
- q() { this.search(); },
- selectedTags: {
- handler() {
- this.search();
- },
- deep: true
- },
- },
-
- methods: {
- search() {
- if ((this.q === '' || this.q == null) && this.selectedTags.size === 0) {
- this.searchEmojis = null;
- return;
- }
-
- if (this.selectedTags.size === 0) {
- this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q));
- } else {
- this.searchEmojis = this.customEmojis.filter(e => (e.name.includes(this.q) || e.aliases.includes(this.q)) && [...this.selectedTags].every(t => e.aliases.includes(t)));
- }
- },
-
- toggleTag(tag) {
- if (this.selectedTags.has(tag)) {
- this.selectedTags.delete(tag);
- } else {
- this.selectedTags.add(tag);
- }
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.driuhtrh {
- background: var(--bg);
-
- > .query {
- background: var(--bg);
- padding: 16px;
-
- > .tags {
- > .tag {
- display: inline-block;
- margin: 8px 8px 0 0;
- padding: 4px 8px;
- font-size: 0.9em;
- background: var(--accentedBg);
- border-radius: 5px;
-
- &.active {
- background: var(--accent);
- color: var(--fgOnAccent);
- }
- }
- }
- }
-
- > .emojis {
- --x-padding: 0 16px;
-
- .zuvgdzyt {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
- grid-gap: 12px;
- margin: 0 var(--margin) var(--margin) var(--margin);
- }
- }
-}
-</style>
diff --git a/src/client/pages/emojis.emoji.vue b/src/client/pages/emojis.emoji.vue
deleted file mode 100644
index ca0ef2dbb7..0000000000
--- a/src/client/pages/emojis.emoji.vue
+++ /dev/null
@@ -1,94 +0,0 @@
-<template>
-<button class="zuvgdzyu _button" @click="menu">
- <img :src="emoji.url" class="img" :alt="emoji.name"/>
- <div class="body">
- <div class="name _monospace">{{ emoji.name }}</div>
- <div class="info">{{ emoji.aliases.join(' ') }}</div>
- </div>
-</button>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@client/os';
-import copyToClipboard from '@client/scripts/copy-to-clipboard';
-import VanillaTilt from 'vanilla-tilt';
-
-export default defineComponent({
- props: {
- emoji: {
- type: Object,
- required: true,
- }
- },
-
- mounted() {
- if (this.$store.animation) {
- VanillaTilt.init(this.$el, {
- reverse: true,
- gyroscope: false,
- scale: 1.1,
- speed: 500,
- });
- }
- },
-
- 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);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.zuvgdzyu {
- display: flex;
- align-items: center;
- padding: 12px;
- text-align: left;
- background: var(--panel);
- border-radius: 8px;
- transform-style: preserve-3d;
- transform: perspective(1000px);
-
- &:hover {
- border-color: var(--accent);
- }
-
- > .img {
- width: 42px;
- height: 42px;
- transform: translateZ(20px);
- }
-
- > .body {
- padding: 0 0 0 8px;
- white-space: nowrap;
- overflow: hidden;
- transform: translateZ(10px);
-
- > .name {
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- > .info {
- opacity: 0.5;
- font-size: 0.9em;
- text-overflow: ellipsis;
- overflow: hidden;
- }
- }
-}
-</style>
diff --git a/src/client/pages/emojis.vue b/src/client/pages/emojis.vue
deleted file mode 100644
index 8918de2338..0000000000
--- a/src/client/pages/emojis.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-<template>
-<div :class="$style.root">
- <XCategory v-if="tab === 'category'"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, computed } from 'vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import XCategory from './emojis.category.vue';
-
-export default defineComponent({
- components: {
- XCategory,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => ({
- title: this.$ts.customEmojis,
- icon: 'fas fa-laugh',
- bg: 'var(--bg)',
- })),
- tab: 'category',
- }
- },
-});
-</script>
-
-<style lang="scss" module>
-.root {
- max-width: 1000px;
- margin: 0 auto;
-}
-</style>
diff --git a/src/client/pages/explore.vue b/src/client/pages/explore.vue
deleted file mode 100644
index a77b4e53c3..0000000000
--- a/src/client/pages/explore.vue
+++ /dev/null
@@ -1,261 +0,0 @@
-<template>
-<div>
- <MkSpacer :content-max="1200">
- <div class="lznhrdub">
- <div v-if="tab === 'local'">
- <div class="localfedi7 _block _isolated" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
- <header><span>{{ $t('explore', { host: meta.name || 'Misskey' }) }}</span></header>
- <div><span>{{ $t('exploreUsersCount', { count: num(stats.originalUsersCount) }) }}</span></div>
- </div>
-
- <template v-if="tag == null">
- <MkFolder class="_gap" persist-key="explore-pinned-users">
- <template #header><i class="fas fa-bookmark fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.pinnedUsers }}</template>
- <XUserList :pagination="pinnedUsers"/>
- </MkFolder>
- <MkFolder class="_gap" persist-key="explore-popular-users">
- <template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template>
- <XUserList :pagination="popularUsers"/>
- </MkFolder>
- <MkFolder class="_gap" persist-key="explore-recently-updated-users">
- <template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template>
- <XUserList :pagination="recentlyUpdatedUsers"/>
- </MkFolder>
- <MkFolder class="_gap" persist-key="explore-recently-registered-users">
- <template #header><i class="fas fa-plus fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyRegisteredUsers }}</template>
- <XUserList :pagination="recentlyRegisteredUsers"/>
- </MkFolder>
- </template>
- </div>
- <div v-else-if="tab === 'remote'">
- <div class="localfedi7 _block _isolated" v-if="tag == null" :style="{ backgroundImage: `url(/static-assets/client/fedi.jpg)` }">
- <header><span>{{ $ts.exploreFediverse }}</span></header>
- </div>
-
- <MkFolder :foldable="true" :expanded="false" ref="tags" class="_gap">
- <template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularTags }}</template>
-
- <div class="vxjfqztj">
- <MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA>
- <MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA>
- </div>
- </MkFolder>
-
- <MkFolder v-if="tag != null" :key="`${tag}`" class="_gap">
- <template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template>
- <XUserList :pagination="tagUsers"/>
- </MkFolder>
-
- <template v-if="tag == null">
- <MkFolder class="_gap">
- <template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template>
- <XUserList :pagination="popularUsersF"/>
- </MkFolder>
- <MkFolder class="_gap">
- <template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template>
- <XUserList :pagination="recentlyUpdatedUsersF"/>
- </MkFolder>
- <MkFolder class="_gap">
- <template #header><i class="fas fa-rocket fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyDiscoveredUsers }}</template>
- <XUserList :pagination="recentlyRegisteredUsersF"/>
- </MkFolder>
- </template>
- </div>
- <div v-else-if="tab === 'search'">
- <div class="_isolated">
- <MkInput v-model="searchQuery" :debounce="true" type="search">
- <template #prefix><i class="fas fa-search"></i></template>
- <template #label>{{ $ts.searchUser }}</template>
- </MkInput>
- <MkRadios v-model="searchOrigin">
- <option value="local">{{ $ts.local }}</option>
- <option value="remote">{{ $ts.remote }}</option>
- <option value="both">{{ $ts.both }}</option>
- </MkRadios>
- </div>
-
- <XUserList v-if="searchQuery" class="_gap" :pagination="searchPagination" ref="search"/>
- </div>
- </div>
- </MkSpacer>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import XUserList from '@client/components/user-list.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkRadios from '@client/components/form/radios.vue';
-import number from '@client/filters/number';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XUserList,
- MkFolder,
- MkInput,
- MkRadios,
- },
-
- props: {
- tag: {
- type: String,
- required: false
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => ({
- title: this.$ts.explore,
- icon: 'fas fa-hashtag',
- bg: 'var(--bg)',
- 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'; },
- }, {
- active: this.tab === 'search',
- title: this.$ts.search,
- onClick: () => { this.tab = 'search'; },
- },]
- })),
- tab: 'local',
- pinnedUsers: { endpoint: 'pinned-users' },
- popularUsers: { endpoint: 'users', limit: 10, noPaging: true, params: {
- state: 'alive',
- origin: 'local',
- sort: '+follower',
- } },
- recentlyUpdatedUsers: { endpoint: 'users', limit: 10, noPaging: true, params: {
- origin: 'local',
- sort: '+updatedAt',
- } },
- recentlyRegisteredUsers: { endpoint: 'users', limit: 10, noPaging: true, params: {
- origin: 'local',
- state: 'alive',
- sort: '+createdAt',
- } },
- popularUsersF: { endpoint: 'users', limit: 10, noPaging: true, params: {
- state: 'alive',
- origin: 'remote',
- sort: '+follower',
- } },
- recentlyUpdatedUsersF: { endpoint: 'users', limit: 10, noPaging: true, params: {
- origin: 'combined',
- sort: '+updatedAt',
- } },
- recentlyRegisteredUsersF: { endpoint: 'users', limit: 10, noPaging: true, params: {
- origin: 'combined',
- sort: '+createdAt',
- } },
- searchPagination: {
- endpoint: 'users/search',
- limit: 10,
- params: computed(() => (this.searchQuery && this.searchQuery !== '') ? {
- query: this.searchQuery,
- origin: this.searchOrigin,
- } : null)
- },
- tagsLocal: [],
- tagsRemote: [],
- stats: null,
- searchQuery: null,
- searchOrigin: 'combined',
- num: number,
- };
- },
-
- computed: {
- meta() {
- return this.$instance;
- },
- tagUsers(): any {
- return {
- endpoint: 'hashtags/users',
- limit: 30,
- params: {
- tag: this.tag,
- origin: 'combined',
- sort: '+follower',
- }
- };
- },
- },
-
- watch: {
- tag() {
- if (this.$refs.tags) this.$refs.tags.toggleContent(this.tag == null);
- },
- },
-
- created() {
- os.api('hashtags/list', {
- sort: '+attachedLocalUsers',
- attachedToLocalUserOnly: true,
- limit: 30
- }).then(tags => {
- this.tagsLocal = tags;
- });
- os.api('hashtags/list', {
- sort: '+attachedRemoteUsers',
- attachedToRemoteUserOnly: true,
- limit: 30
- }).then(tags => {
- this.tagsRemote = tags;
- });
- os.api('stats').then(stats => {
- this.stats = stats;
- });
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.localfedi7 {
- color: #fff;
- padding: 16px;
- height: 80px;
- background-position: 50%;
- background-size: cover;
- margin-bottom: var(--margin);
-
- > * {
- &:not(:last-child) {
- margin-bottom: 8px;
- }
-
- > span {
- display: inline-block;
- padding: 6px 8px;
- background: rgba(0, 0, 0, 0.7);
- }
- }
-
- > header {
- font-size: 20px;
- font-weight: bold;
- }
-
- > div {
- font-size: 14px;
- opacity: 0.8;
- }
-}
-
-.vxjfqztj {
- > * {
- margin-right: 16px;
-
- &.local {
- font-weight: bold;
- }
- }
-}
-</style>
diff --git a/src/client/pages/favorites.vue b/src/client/pages/favorites.vue
deleted file mode 100644
index f13723c2d1..0000000000
--- a/src/client/pages/favorites.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-<template>
-<div class="jmelgwjh">
- <div class="body">
- <XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'" @before="before()" @after="after()"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import XNotes from '@client/components/notes.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XNotes
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.favorites,
- icon: 'fas fa-star',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'i/favorites',
- limit: 10,
- params: () => ({
- })
- },
- };
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.jmelgwjh {
- background: var(--bg);
-
- > .body {
- box-sizing: border-box;
- max-width: 800px;
- margin: 0 auto;
- padding: 16px;
- }
-}
-</style>
diff --git a/src/client/pages/featured.vue b/src/client/pages/featured.vue
deleted file mode 100644
index 50df26bfb1..0000000000
--- a/src/client/pages/featured.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<template>
-<MkSpacer :content-max="800">
- <XNotes ref="notes" :pagination="pagination" @before="before" @after="after"/>
-</MkSpacer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import XNotes from '@client/components/notes.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XNotes
- },
-
- 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,
- },
- };
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- }
- }
-});
-</script>
diff --git a/src/client/pages/federation.vue b/src/client/pages/federation.vue
deleted file mode 100644
index eae6a05367..0000000000
--- a/src/client/pages/federation.vue
+++ /dev/null
@@ -1,265 +0,0 @@
-<template>
-<div class="taeiyria">
- <div class="query">
- <MkInput v-model="host" :debounce="true" class="">
- <template #prefix><i class="fas fa-search"></i></template>
- <template #label>{{ $ts.host }}</template>
- </MkInput>
- <div class="_inputSplit">
- <MkSelect v-model="state">
- <template #label>{{ $ts.state }}</template>
- <option value="all">{{ $ts.all }}</option>
- <option value="federating">{{ $ts.federating }}</option>
- <option value="subscribing">{{ $ts.subscribing }}</option>
- <option value="publishing">{{ $ts.publishing }}</option>
- <option value="suspended">{{ $ts.suspended }}</option>
- <option value="blocked">{{ $ts.blocked }}</option>
- <option value="notResponding">{{ $ts.notResponding }}</option>
- </MkSelect>
- <MkSelect v-model="sort">
- <template #label>{{ $ts.sort }}</template>
- <option value="+pubSub">{{ $ts.pubSub }} ({{ $ts.descendingOrder }})</option>
- <option value="-pubSub">{{ $ts.pubSub }} ({{ $ts.ascendingOrder }})</option>
- <option value="+notes">{{ $ts.notes }} ({{ $ts.descendingOrder }})</option>
- <option value="-notes">{{ $ts.notes }} ({{ $ts.ascendingOrder }})</option>
- <option value="+users">{{ $ts.users }} ({{ $ts.descendingOrder }})</option>
- <option value="-users">{{ $ts.users }} ({{ $ts.ascendingOrder }})</option>
- <option value="+following">{{ $ts.following }} ({{ $ts.descendingOrder }})</option>
- <option value="-following">{{ $ts.following }} ({{ $ts.ascendingOrder }})</option>
- <option value="+followers">{{ $ts.followers }} ({{ $ts.descendingOrder }})</option>
- <option value="-followers">{{ $ts.followers }} ({{ $ts.ascendingOrder }})</option>
- <option value="+caughtAt">{{ $ts.registeredAt }} ({{ $ts.descendingOrder }})</option>
- <option value="-caughtAt">{{ $ts.registeredAt }} ({{ $ts.ascendingOrder }})</option>
- <option value="+lastCommunicatedAt">{{ $ts.lastCommunication }} ({{ $ts.descendingOrder }})</option>
- <option value="-lastCommunicatedAt">{{ $ts.lastCommunication }} ({{ $ts.ascendingOrder }})</option>
- <option value="+driveUsage">{{ $ts.driveUsage }} ({{ $ts.descendingOrder }})</option>
- <option value="-driveUsage">{{ $ts.driveUsage }} ({{ $ts.ascendingOrder }})</option>
- <option value="+driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.descendingOrder }})</option>
- <option value="-driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.ascendingOrder }})</option>
- </MkSelect>
- </div>
- </div>
-
- <MkPagination :pagination="pagination" #default="{items}" ref="instances" :key="host + state">
- <div class="dqokceoi">
- <MkA class="instance" v-for="instance in items" :key="instance.id" :to="`/instance-info/${instance.host}`">
- <div class="host"><img :src="instance.faviconUrl">{{ instance.host }}</div>
- <div class="table">
- <div class="cell">
- <div class="key">{{ $ts.registeredAt }}</div>
- <div class="value"><MkTime :time="instance.caughtAt"/></div>
- </div>
- <div class="cell">
- <div class="key">{{ $ts.software }}</div>
- <div class="value">{{ instance.softwareName || `(${$ts.unknown})` }}</div>
- </div>
- <div class="cell">
- <div class="key">{{ $ts.version }}</div>
- <div class="value">{{ instance.softwareVersion || `(${$ts.unknown})` }}</div>
- </div>
- <div class="cell">
- <div class="key">{{ $ts.users }}</div>
- <div class="value">{{ instance.usersCount }}</div>
- </div>
- <div class="cell">
- <div class="key">{{ $ts.notes }}</div>
- <div class="value">{{ instance.notesCount }}</div>
- </div>
- <div class="cell">
- <div class="key">{{ $ts.sent }}</div>
- <div class="value"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div>
- </div>
- <div class="cell">
- <div class="key">{{ $ts.received }}</div>
- <div class="value"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div>
- </div>
- </div>
- <div class="footer">
- <span class="status" :class="getStatus(instance)">{{ getStatus(instance) }}</span>
- <span class="pubSub">
- <span class="sub" v-if="instance.followersCount > 0"><i class="fas fa-caret-down icon"></i>Sub</span>
- <span class="sub" v-else><i class="fas fa-caret-down icon"></i>-</span>
- <span class="pub" v-if="instance.followingCount > 0"><i class="fas fa-caret-up icon"></i>Pub</span>
- <span class="pub" v-else><i class="fas fa-caret-up icon"></i>-</span>
- </span>
- <span class="right">
- <span class="latestStatus">{{ instance.latestStatus || '-' }}</span>
- <span class="lastCommunicatedAt"><MkTime :time="instance.lastCommunicatedAt"/></span>
- </span>
- </div>
- </MkA>
- </div>
- </MkPagination>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-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 } :
- {})
- })
- },
- }
- },
-
- watch: {
- host() {
- this.$refs.instances.reload();
- },
- state() {
- this.$refs.instances.reload();
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- getStatus(instance) {
- if (instance.isSuspended) return 'suspended';
- if (instance.isNotResponding) return 'error';
- return 'alive';
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.taeiyria {
- > .query {
- background: var(--bg);
- padding: 16px;
- }
-}
-
-.dqokceoi {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
- grid-gap: 12px;
- padding: 16px;
-
- > .instance {
- padding: 16px;
- border: solid 1px var(--divider);
- border-radius: 6px;
-
- &:hover {
- border: solid 1px var(--accent);
- text-decoration: none;
- }
-
- > .host {
- font-weight: bold;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-
- > img {
- width: 18px;
- height: 18px;
- margin-right: 6px;
- vertical-align: middle;
- }
- }
-
- > .table {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
- grid-gap: 6px;
- margin: 6px 0;
- font-size: 70%;
-
- > .cell {
- > .key, > .value {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- > .key {
- opacity: 0.7;
- }
-
- > .value {
- }
- }
- }
-
- > .footer {
- display: flex;
- align-items: center;
- font-size: 0.9em;
-
- > .status {
- &.suspended {
- opacity: 0.5;
- }
-
- &.error {
- color: var(--error);
- }
-
- &.alive {
- color: var(--success);
- }
- }
-
- > .pubSub {
- margin-left: 8px;
- }
-
- > .right {
- margin-left: auto;
-
- > .latestStatus {
- border: solid 1px var(--divider);
- border-radius: 4px;
- margin: 0 8px;
- padding: 0 4px;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/follow-requests.vue b/src/client/pages/follow-requests.vue
deleted file mode 100644
index 6115dda454..0000000000
--- a/src/client/pages/follow-requests.vue
+++ /dev/null
@@ -1,153 +0,0 @@
-<template>
-<div>
- <MkPagination :pagination="pagination" class="mk-follow-requests" ref="list">
- <template #empty>
- <div class="_fullinfo">
- <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ $ts.noFollowRequests }}</div>
- </div>
- </template>
- <template #default="{items}">
- <div class="user _panel" v-for="req in items" :key="req.id">
- <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/>
- <div class="body">
- <div class="name">
- <MkA class="name" :to="userPage(req.follower)" v-user-preview="req.follower.id"><MkUserName :user="req.follower"/></MkA>
- <p class="acct">@{{ acct(req.follower) }}</p>
- </div>
- <div class="description" v-if="req.follower.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>
- </template>
- </MkPagination>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import { userPage, acct } from '@client/filters/user';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkPagination
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.followRequests,
- icon: 'fas fa-user-clock',
- },
- pagination: {
- endpoint: 'following/requests/list',
- 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
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-follow-requests {
- > .user {
- display: flex;
- padding: 16px;
-
- > .avatar {
- display: block;
- flex-shrink: 0;
- margin: 0 12px 0 0;
- width: 42px;
- height: 42px;
- border-radius: 8px;
- }
-
- > .body {
- display: flex;
- width: calc(100% - 54px);
- position: relative;
-
- > .name {
- width: 45%;
-
- @media (max-width: 500px) {
- width: 100%;
- }
-
- > .name,
- > .acct {
- display: block;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- margin: 0;
- }
-
- > .name {
- font-size: 16px;
- line-height: 24px;
- }
-
- > .acct {
- font-size: 15px;
- line-height: 16px;
- opacity: 0.7;
- }
- }
-
- > .description {
- width: 55%;
- line-height: 42px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- opacity: 0.7;
- font-size: 14px;
- padding-right: 40px;
- padding-left: 8px;
- box-sizing: border-box;
-
- @media (max-width: 500px) {
- display: none;
- }
- }
-
- > .actions {
- position: absolute;
- top: 0;
- bottom: 0;
- right: 0;
- margin: auto 0;
-
- > button {
- padding: 12px;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/follow.vue b/src/client/pages/follow.vue
deleted file mode 100644
index d5247aff1e..0000000000
--- a/src/client/pages/follow.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<div class="mk-follow-page">
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@client/os';
-import { parseAcct } from '@/misc/acct';
-
-export default defineComponent({
- created() {
- const acct = new URL(location.href).searchParams.get('acct');
- if (acct == null) return;
-
- let promise;
-
- if (acct.startsWith('https://')) {
- promise = os.api('ap/show', {
- uri: acct
- });
- promise.then(res => {
- if (res.type == 'User') {
- this.follow(res.object);
- } else if (res.type === 'Note') {
- this.$router.push(`/notes/${res.object.id}`);
- } else {
- os.dialog({
- type: 'error',
- text: 'Not a user'
- }).then(() => {
- window.close();
- });
- }
- });
- } else {
- promise = os.api('users/show', parseAcct(acct));
- promise.then(user => {
- this.follow(user);
- });
- }
-
- os.promiseDialog(promise, null, null, this.$ts.fetchingAsApObject);
- },
-
- methods: {
- async follow(user) {
- const { canceled } = await os.dialog({
- type: 'question',
- text: this.$t('followConfirm', { name: user.name || user.username }),
- showCancelButton: true
- });
-
- if (canceled) {
- window.close();
- return;
- }
-
- os.apiWithDialog('following/create', {
- userId: user.id
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/gallery/edit.vue b/src/client/pages/gallery/edit.vue
deleted file mode 100644
index 8e74b068ef..0000000000
--- a/src/client/pages/gallery/edit.vue
+++ /dev/null
@@ -1,168 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormInput v-model="title">
- <span>{{ $ts.title }}</span>
- </FormInput>
-
- <FormTextarea v-model="description" :max="500">
- <span>{{ $ts.description }}</span>
- </FormTextarea>
-
- <FormGroup>
- <div v-for="file in files" :key="file.id" class="_debobigegoItem _debobigegoPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
- <div class="name">{{ file.name }}</div>
- <button class="remove _button" @click="remove(file)" v-tooltip="$ts.remove"><i class="fas fa-times"></i></button>
- </div>
- <FormButton @click="selectFile" primary><i class="fas fa-plus"></i> {{ $ts.attachFile }}</FormButton>
- </FormGroup>
-
- <FormSwitch v-model="isSensitive">{{ $ts.markAsSensitive }}</FormSwitch>
-
- <FormButton v-if="postId" @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- <FormButton v-else @click="save" primary><i class="fas fa-save"></i> {{ $ts.publish }}</FormButton>
-
- <FormButton v-if="postId" @click="del" danger><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormTuple from '@client/components/debobigego/tuple.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import { selectFile } from '@client/scripts/select-file';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormButton,
- FormInput,
- FormTextarea,
- FormSwitch,
- FormBase,
- FormGroup,
- FormSuspense,
- },
-
- props: {
- postId: {
- type: String,
- required: false,
- default: null,
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.postId ? {
- title: this.$ts.edit,
- icon: 'fas fa-pencil-alt'
- } : {
- title: this.$ts.postToGallery,
- icon: 'fas fa-pencil-alt'
- }),
- init: null,
- files: [],
- description: null,
- title: null,
- isSensitive: false,
- }
- },
-
- watch: {
- postId: {
- handler() {
- this.init = () => this.postId ? os.api('gallery/posts/show', {
- postId: this.postId
- }).then(post => {
- this.files = post.files;
- this.title = post.title;
- this.description = post.description;
- this.isSensitive = post.isSensitive;
- }) : Promise.resolve(null);
- },
- immediate: true,
- }
- },
-
- methods: {
- selectFile(e) {
- selectFile(e.currentTarget || e.target, null, true).then(files => {
- this.files = this.files.concat(files);
- });
- },
-
- remove(file) {
- this.files = this.files.filter(f => f.id !== file.id);
- },
-
- async save() {
- if (this.postId) {
- await os.apiWithDialog('gallery/posts/update', {
- postId: this.postId,
- title: this.title,
- description: this.description,
- fileIds: this.files.map(file => file.id),
- isSensitive: this.isSensitive,
- });
- this.$router.push(`/gallery/${this.postId}`);
- } else {
- const post = await os.apiWithDialog('gallery/posts/create', {
- title: this.title,
- description: this.description,
- fileIds: this.files.map(file => file.id),
- isSensitive: this.isSensitive,
- });
- this.$router.push(`/gallery/${post.id}`);
- }
- },
-
- async del() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$ts.deleteConfirm,
- showCancelButton: true
- });
- if (canceled) return;
- await os.apiWithDialog('gallery/posts/delete', {
- postId: this.postId,
- });
- this.$router.push(`/gallery`);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.wqugxsfx {
- height: 200px;
- background-size: contain;
- background-position: center;
- background-repeat: no-repeat;
- position: relative;
-
- > .name {
- position: absolute;
- top: 8px;
- left: 9px;
- padding: 8px;
- background: var(--panel);
- }
-
- > .remove {
- position: absolute;
- top: 8px;
- right: 9px;
- padding: 8px;
- background: var(--panel);
- }
-}
-</style>
diff --git a/src/client/pages/gallery/index.vue b/src/client/pages/gallery/index.vue
deleted file mode 100644
index ffc599513e..0000000000
--- a/src/client/pages/gallery/index.vue
+++ /dev/null
@@ -1,152 +0,0 @@
-<template>
-<div class="xprsixdl _root">
- <MkTab v-model="tab" v-if="$i">
- <option value="explore"><i class="fas fa-icons"></i> {{ $ts.gallery }}</option>
- <option value="liked"><i class="fas fa-heart"></i> {{ $ts._gallery.liked }}</option>
- <option value="my"><i class="fas fa-edit"></i> {{ $ts._gallery.my }}</option>
- </MkTab>
-
- <div v-if="tab === 'explore'">
- <MkFolder class="_gap">
- <template #header><i class="fas fa-clock"></i>{{ $ts.recentPosts }}</template>
- <MkPagination :pagination="recentPostsPagination" #default="{items}" :disable-auto-load="true">
- <div class="vfpdbgtk">
- <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/>
- </div>
- </MkPagination>
- </MkFolder>
- <MkFolder class="_gap">
- <template #header><i class="fas fa-fire-alt"></i>{{ $ts.popularPosts }}</template>
- <MkPagination :pagination="popularPostsPagination" #default="{items}" :disable-auto-load="true">
- <div class="vfpdbgtk">
- <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/>
- </div>
- </MkPagination>
- </MkFolder>
- </div>
- <div v-else-if="tab === 'liked'">
- <MkPagination :pagination="likedPostsPagination" #default="{items}">
- <div class="vfpdbgtk">
- <MkGalleryPostPreview v-for="like in items" :post="like.post" :key="like.id" class="post"/>
- </div>
- </MkPagination>
- </div>
- <div v-else-if="tab === 'my'">
- <MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="fas fa-plus"></i> {{ $ts.postToGallery }}</MkA>
- <MkPagination :pagination="myPostsPagination" #default="{items}">
- <div class="vfpdbgtk">
- <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/>
- </div>
- </MkPagination>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import XUserList from '@client/components/user-list.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkTab from '@client/components/tab.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkGalleryPostPreview from '@client/components/gallery-post-preview.vue';
-import number from '@client/filters/number';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XUserList,
- MkFolder,
- MkInput,
- MkButton,
- MkTab,
- MkPagination,
- MkGalleryPostPreview,
- },
-
- props: {
- tag: {
- type: String,
- required: false
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.gallery,
- icon: 'fas fa-icons'
- },
- tab: 'explore',
- recentPostsPagination: {
- endpoint: 'gallery/posts',
- limit: 6,
- },
- popularPostsPagination: {
- endpoint: 'gallery/featured',
- limit: 5,
- },
- myPostsPagination: {
- endpoint: 'i/gallery/posts',
- limit: 5,
- },
- likedPostsPagination: {
- endpoint: 'i/gallery/likes',
- limit: 5,
- },
- tags: [],
- };
- },
-
- computed: {
- meta() {
- return this.$instance;
- },
- tagUsers(): any {
- return {
- endpoint: 'hashtags/users',
- limit: 30,
- params: {
- tag: this.tag,
- origin: 'combined',
- sort: '+follower',
- }
- };
- },
- },
-
- watch: {
- tag() {
- if (this.$refs.tags) this.$refs.tags.toggleContent(this.tag == null);
- },
- },
-
- created() {
-
- },
-
- methods: {
-
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.xprsixdl {
- max-width: 1400px;
- margin: 0 auto;
-}
-
-.vfpdbgtk {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
- grid-gap: 12px;
- margin: 0 var(--margin);
-
- > .post {
-
- }
-}
-</style>
diff --git a/src/client/pages/gallery/post.vue b/src/client/pages/gallery/post.vue
deleted file mode 100644
index dbac003e38..0000000000
--- a/src/client/pages/gallery/post.vue
+++ /dev/null
@@ -1,282 +0,0 @@
-<template>
-<div class="_root">
- <transition name="fade" mode="out-in">
- <div v-if="post" class="rkxwuolj">
- <div class="files">
- <div class="file" v-for="file in post.files" :key="file.id">
- <img :src="file.url"/>
- </div>
- </div>
- <div class="body _block">
- <div class="title">{{ post.title }}</div>
- <div class="description"><Mfm :text="post.description"/></div>
- <div class="info">
- <i class="fas fa-clock"></i> <MkTime :time="post.createdAt" mode="detail"/>
- </div>
- <div class="actions">
- <div class="like">
- <MkButton class="button" @click="unlike()" v-if="post.isLiked" v-tooltip="$ts._gallery.unlike" primary><i class="fas fa-heart"></i><span class="count" v-if="post.likedCount > 0">{{ post.likedCount }}</span></MkButton>
- <MkButton class="button" @click="like()" v-else v-tooltip="$ts._gallery.like"><i class="far fa-heart"></i><span class="count" v-if="post.likedCount > 0">{{ post.likedCount }}</span></MkButton>
- </div>
- <div class="other">
- <button v-if="$i && $i.id === post.user.id" class="_button" @click="edit" v-tooltip="$ts.edit" v-click-anime><i class="fas fa-pencil-alt fa-fw"></i></button>
- <button class="_button" @click="shareWithNote" v-tooltip="$ts.shareWithNote" v-click-anime><i class="fas fa-retweet fa-fw"></i></button>
- <button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button>
- </div>
- </div>
- <div class="user">
- <MkAvatar :user="post.user" class="avatar"/>
- <div class="name">
- <MkUserName :user="post.user" style="display: block;"/>
- <MkAcct :user="post.user"/>
- </div>
- <MkFollowButton v-if="!$i || $i.id != post.user.id" :user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
- </div>
- </div>
- <MkAd :prefer="['horizontal', 'horizontal-big']"/>
- <MkContainer :max-height="300" :foldable="true" class="other">
- <template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
- <MkPagination :pagination="otherPostsPagination" #default="{items}">
- <div class="sdrarzaf">
- <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/>
- </div>
- </MkPagination>
- </MkContainer>
- </div>
- <MkError v-else-if="error" @retry="fetch()"/>
- <MkLoading v-else/>
- </transition>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import MkContainer from '@client/components/ui/container.vue';
-import ImgWithBlurhash from '@client/components/img-with-blurhash.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkGalleryPostPreview from '@client/components/gallery-post-preview.vue';
-import MkFollowButton from '@client/components/follow-button.vue';
-import { url } from '@client/config';
-
-export default defineComponent({
- components: {
- MkContainer,
- ImgWithBlurhash,
- MkPagination,
- MkGalleryPostPreview,
- MkButton,
- MkFollowButton,
- },
- props: {
- postId: {
- type: String,
- required: true
- }
- },
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.post ? {
- title: this.post.title,
- avatar: this.post.user,
- path: `/gallery/${this.post.id}`,
- share: {
- title: this.post.title,
- text: this.post.description,
- },
- actions: [{
- icon: 'fas fa-pencil-alt',
- text: this.$ts.edit,
- handler: this.edit
- }]
- } : null),
- otherPostsPagination: {
- endpoint: 'users/gallery/posts',
- limit: 6,
- params: () => ({
- userId: this.post.user.id
- })
- },
- post: null,
- error: null,
- };
- },
-
- watch: {
- postId: 'fetch'
- },
-
- created() {
- this.fetch();
- },
-
- methods: {
- fetch() {
- this.post = null;
- os.api('gallery/posts/show', {
- postId: this.postId
- }).then(post => {
- this.post = post;
- }).catch(e => {
- this.error = e;
- });
- },
-
- share() {
- navigator.share({
- title: this.post.title,
- text: this.post.description,
- url: `${url}/gallery/${this.post.id}`
- });
- },
-
- shareWithNote() {
- os.post({
- initialText: `${this.post.title} ${url}/gallery/${this.post.id}`
- });
- },
-
- like() {
- os.apiWithDialog('gallery/posts/like', {
- postId: this.postId,
- }).then(() => {
- this.post.isLiked = true;
- this.post.likedCount++;
- });
- },
-
- async unlike() {
- const confirm = await os.dialog({
- type: 'warning',
- showCancelButton: true,
- text: this.$ts.unlikeConfirm,
- });
- if (confirm.canceled) return;
- os.apiWithDialog('gallery/posts/unlike', {
- postId: this.postId,
- }).then(() => {
- this.post.isLiked = false;
- this.post.likedCount--;
- });
- },
-
- edit() {
- this.$router.push(`/gallery/${this.post.id}/edit`);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
- transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
- opacity: 0;
-}
-
-.rkxwuolj {
- > .files {
- > .file {
- > img {
- display: block;
- max-width: 100%;
- max-height: 500px;
- margin: 0 auto;
- }
-
- & + .file {
- margin-top: 16px;
- }
- }
- }
-
- > .body {
- padding: 32px;
-
- > .title {
- font-weight: bold;
- font-size: 1.2em;
- margin-bottom: 16px;
- }
-
- > .info {
- margin-top: 16px;
- font-size: 90%;
- opacity: 0.7;
- }
-
- > .actions {
- display: flex;
- align-items: center;
- margin-top: 16px;
- padding: 16px 0 0 0;
- border-top: solid 0.5px var(--divider);
-
- > .like {
- > .button {
- --accent: rgb(241 97 132);
- --X8: rgb(241 92 128);
- --buttonBg: rgb(216 71 106 / 5%);
- --buttonHoverBg: rgb(216 71 106 / 10%);
- color: #ff002f;
-
- ::v-deep(.count) {
- margin-left: 0.5em;
- }
- }
- }
-
- > .other {
- margin-left: auto;
-
- > button {
- padding: 8px;
- margin: 0 8px;
-
- &:hover {
- color: var(--fgHighlighted);
- }
- }
- }
- }
-
- > .user {
- margin-top: 16px;
- padding: 16px 0 0 0;
- border-top: solid 0.5px var(--divider);
- display: flex;
- align-items: center;
-
- > .avatar {
- width: 52px;
- height: 52px;
- }
-
- > .name {
- margin: 0 0 0 12px;
- font-size: 90%;
- }
-
- > .koudoku {
- margin-left: auto;
- }
- }
- }
-}
-
-.sdrarzaf {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
- grid-gap: 12px;
- margin: var(--margin);
-
- > .post {
-
- }
-}
-</style>
diff --git a/src/client/pages/instance-info.vue b/src/client/pages/instance-info.vue
deleted file mode 100644
index 291ceb5dfd..0000000000
--- a/src/client/pages/instance-info.vue
+++ /dev/null
@@ -1,238 +0,0 @@
-<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" @click="info" primary>{{ $ts.settings }}</FormButton>
-
- <FormTextarea readonly :value="instance.description">
- <span>{{ $ts.description }}</span>
- </FormTextarea>
-
- <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>
- <template #key>{{ $ts.latestRequestSentAt }}</template>
- <template #value><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.latestStatus }}</template>
- <template #value>{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <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>
- <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">
- <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 class="chart">
- <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :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>
- <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 :p="dnsPromiseFactory" v-slot="{ result: dns }">
- <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>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import MkChart from '@client/components/chart.vue';
-import FormObjectView from '@client/components/debobigego/object-view.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import MkSelect from '@client/components/form/select.vue';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-import bytes from '@client/filters/bytes';
-import * as symbols from '@client/symbols';
-import MkInstanceInfo from '@client/pages/admin/instance.vue';
-
-export default defineComponent({
- components: {
- FormBase,
- FormTextarea,
- FormObjectView,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- FormSuspense,
- MkSelect,
- MkChart,
- },
-
- props: {
- host: {
- type: String,
- required: true
- }
- },
-
- 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',
- }
- },
-
- mounted() {
- this.fetch();
- },
-
- methods: {
- number,
- bytes,
-
- async fetch() {
- this.instance = await os.api('federation/show-instance', {
- host: this.host
- });
- },
-
- info() {
- os.popup(MkInstanceInfo, {
- instance: this.instance
- }, {}, 'closed');
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.fnfelxur {
- padding: 16px;
-
- > .icon {
- display: block;
- margin: auto;
- height: 64px;
- border-radius: 8px;
- }
-}
-
-.cmhjzshl {
- > .selects {
- display: flex;
- padding: 16px;
- }
-}
-</style>
diff --git a/src/client/pages/mentions.vue b/src/client/pages/mentions.vue
deleted file mode 100644
index 04682a856a..0000000000
--- a/src/client/pages/mentions.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-<template>
-<MkSpacer :content-max="800">
- <XNotes :pagination="pagination" @before="before()" @after="after()"/>
-</MkSpacer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import XNotes from '@client/components/notes.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XNotes
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.mentions,
- icon: 'fas fa-at',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'notes/mentions',
- limit: 10,
- },
- };
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- }
- }
-});
-</script>
diff --git a/src/client/pages/messages.vue b/src/client/pages/messages.vue
deleted file mode 100644
index e3d668cf45..0000000000
--- a/src/client/pages/messages.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-<template>
-<MkSpacer :content-max="800">
- <XNotes :pagination="pagination" @before="before()" @after="after()"/>
-</MkSpacer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import XNotes from '@client/components/notes.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XNotes
- },
-
- 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'
- })
- },
- };
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- }
- }
-});
-</script>
diff --git a/src/client/pages/messaging/index.vue b/src/client/pages/messaging/index.vue
deleted file mode 100644
index 5b4fd51e55..0000000000
--- a/src/client/pages/messaging/index.vue
+++ /dev/null
@@ -1,307 +0,0 @@
-<template>
-<MkSpacer :content-max="800">
- <div class="yweeujhr" v-size="{ max: [400] }">
- <MkButton @click="start" primary class="start"><i class="fas fa-plus"></i> {{ $ts.startMessaging }}</MkButton>
-
- <div class="history" v-if="messages.length > 0">
- <MkA v-for="(message, i) in messages"
- class="message _block"
- :class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($i.id) : message.isRead }"
- :to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
- :data-index="i"
- :key="message.id"
- v-anim="i"
- >
- <div>
- <MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" :show-indicator="true"/>
- <header v-if="message.groupId">
- <span class="name">{{ message.group.name }}</span>
- <MkTime :time="message.createdAt" class="time"/>
- </header>
- <header v-else>
- <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span>
- <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span>
- <MkTime :time="message.createdAt" class="time"/>
- </header>
- <div class="body">
- <p class="text"><span class="me" v-if="isMe(message)">{{ $ts.you }}:</span>{{ message.text }}</p>
- </div>
- </div>
- </MkA>
- </div>
- <div class="_fullinfo" v-if="!fetching && messages.length == 0">
- <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ $ts.noHistory }}</div>
- </div>
- <MkLoading v-if="fetching"/>
- </div>
-</MkSpacer>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent, markRaw } from 'vue';
-import { getAcct } from '@/misc/acct';
-import MkButton from '@client/components/ui/button.vue';
-import { acct } from '@client/filters/user';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.messaging,
- icon: 'fas fa-comments',
- bg: 'var(--bg)',
- },
- fetching: true,
- moreFetching: false,
- messages: [],
- connection: null,
- };
- },
-
- mounted() {
- this.connection = markRaw(os.stream.useChannel('messagingIndex'));
-
- this.connection.on('message', this.onMessage);
- this.connection.on('read', this.onRead);
-
- os.api('messaging/history', { group: false }).then(userMessages => {
- os.api('messaging/history', { group: true }).then(groupMessages => {
- const messages = userMessages.concat(groupMessages);
- messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
- this.messages = messages;
- this.fetching = false;
- });
- });
- },
-
- beforeUnmount() {
- this.connection.dispose();
- },
-
- methods: {
- getAcct,
-
- isMe(message) {
- return message.userId == this.$i.id;
- },
-
- onMessage(message) {
- if (message.recipientId) {
- this.messages = this.messages.filter(m => !(
- (m.recipientId == message.recipientId && m.userId == message.userId) ||
- (m.recipientId == message.userId && m.userId == message.recipientId)));
-
- this.messages.unshift(message);
- } else if (message.groupId) {
- this.messages = this.messages.filter(m => m.groupId !== message.groupId);
- this.messages.unshift(message);
- }
- },
-
- onRead(ids) {
- for (const id of ids) {
- const found = this.messages.find(m => m.id == id);
- if (found) {
- if (found.recipientId) {
- found.isRead = true;
- } else if (found.groupId) {
- found.reads.push(this.$i.id);
- }
- }
- }
- },
-
- start(ev) {
- os.popupMenu([{
- text: this.$ts.messagingWithUser,
- icon: 'fas fa-user',
- action: () => { this.startUser() }
- }, {
- text: this.$ts.messagingWithGroup,
- icon: 'fas fa-users',
- action: () => { this.startGroup() }
- }], ev.currentTarget || ev.target);
- },
-
- async startUser() {
- os.selectUser().then(user => {
- this.$router.push(`/my/messaging/${getAcct(user)}`);
- });
- },
-
- async startGroup() {
- const groups1 = await os.api('users/groups/owned');
- const groups2 = await os.api('users/groups/joined');
- if (groups1.length === 0 && groups2.length === 0) {
- os.dialog({
- type: 'warning',
- title: this.$ts.youHaveNoGroups,
- text: this.$ts.joinOrCreateGroup,
- });
- return;
- }
- const { canceled, result: group } = await os.dialog({
- type: null,
- title: this.$ts.group,
- select: {
- items: groups1.concat(groups2).map(group => ({
- value: group, text: group.name
- }))
- },
- showCancelButton: true
- });
- if (canceled) return;
- this.$router.push(`/my/messaging/group/${group.id}`);
- },
-
- acct
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.yweeujhr {
-
- > .start {
- margin: 0 auto var(--margin) auto;
- }
-
- > .history {
- > .message {
- display: block;
- text-decoration: none;
- margin-bottom: var(--margin);
-
- * {
- pointer-events: none;
- user-select: none;
- }
-
- &:hover {
- .avatar {
- filter: saturate(200%);
- }
- }
-
- &:active {
- }
-
- &.isRead,
- &.isMe {
- opacity: 0.8;
- }
-
- &:not(.isMe):not(.isRead) {
- > div {
- background-image: url("/static-assets/client/unread.svg");
- background-repeat: no-repeat;
- background-position: 0 center;
- }
- }
-
- &:after {
- content: "";
- display: block;
- clear: both;
- }
-
- > div {
- padding: 20px 30px;
-
- &:after {
- content: "";
- display: block;
- clear: both;
- }
-
- > header {
- display: flex;
- align-items: center;
- margin-bottom: 2px;
- white-space: nowrap;
- overflow: hidden;
-
- > .name {
- margin: 0;
- padding: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- font-size: 1em;
- font-weight: bold;
- transition: all 0.1s ease;
- }
-
- > .username {
- margin: 0 8px;
- }
-
- > .time {
- margin: 0 0 0 auto;
- }
- }
-
- > .avatar {
- float: left;
- width: 54px;
- height: 54px;
- margin: 0 16px 0 0;
- border-radius: 8px;
- transition: all 0.1s ease;
- }
-
- > .body {
-
- > .text {
- display: block;
- margin: 0 0 0 0;
- padding: 0;
- overflow: hidden;
- overflow-wrap: break-word;
- font-size: 1.1em;
- color: var(--faceText);
-
- .me {
- opacity: 0.7;
- }
- }
-
- > .image {
- display: block;
- max-width: 100%;
- max-height: 512px;
- }
- }
- }
- }
- }
-
- &.max-width_400px {
- > .history {
- > .message {
- &:not(.isMe):not(.isRead) {
- > div {
- background-image: none;
- border-left: solid 4px #3aa2dc;
- }
- }
-
- > div {
- padding: 16px;
- font-size: 0.9em;
-
- > .avatar {
- margin: 0 12px 0 0;
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/messaging/messaging-room.form.vue b/src/client/pages/messaging/messaging-room.form.vue
deleted file mode 100644
index 31c42e4ab3..0000000000
--- a/src/client/pages/messaging/messaging-room.form.vue
+++ /dev/null
@@ -1,348 +0,0 @@
-<template>
-<div class="pemppnzi _block"
- @dragover.stop="onDragover"
- @drop.stop="onDrop"
->
- <textarea
- v-model="text"
- ref="text"
- @keypress="onKeypress"
- @compositionupdate="onCompositionUpdate"
- @paste="onPaste"
- :placeholder="$ts.inputMessageHere"
- ></textarea>
- <div class="file" @click="file = null" v-if="file">{{ file.name }}</div>
- <button class="send _button" @click="send" :disabled="!canSend || sending" :title="$ts.send">
- <template v-if="!sending"><i class="fas fa-paper-plane"></i></template><template v-if="sending"><i class="fas fa-spinner fa-pulse fa-fw"></i></template>
- </button>
- <button class="_button" @click="chooseFile"><i class="fas fa-photo-video"></i></button>
- <button class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button>
- <input ref="file" type="file" @change="onChangeFile"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent } from 'vue';
-import insertTextAtCursor from 'insert-text-at-cursor';
-import * as autosize from 'autosize';
-import { formatTimeString } from '@/misc/format-time-string';
-import { selectFile } from '@client/scripts/select-file';
-import * as os from '@client/os';
-import { Autocomplete } from '@client/scripts/autocomplete';
-import { throttle } from 'throttle-debounce';
-
-export default defineComponent({
- props: {
- user: {
- type: Object,
- requird: false,
- },
- group: {
- type: Object,
- requird: false,
- },
- },
- data() {
- return {
- text: null,
- file: null,
- sending: false,
- typing: throttle(3000, () => {
- os.stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id });
- }),
- };
- },
- computed: {
- draftKey(): string {
- return this.user ? 'user:' + this.user.id : 'group:' + this.group.id;
- },
- canSend(): boolean {
- return (this.text != null && this.text != '') || this.file != null;
- },
- room(): any {
- return this.$parent;
- }
- },
- watch: {
- text() {
- this.saveDraft();
- },
- file() {
- this.saveDraft();
- }
- },
- mounted() {
- autosize(this.$refs.text);
-
- // TODO: detach when unmount
- new Autocomplete(this.$refs.text, this, { model: 'text' });
-
- // ๆ›ธใใ‹ใ‘ใฎๆŠ•็จฟใ‚’ๅพฉๅ…ƒ
- const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[this.draftKey];
- if (draft) {
- this.text = draft.data.text;
- this.file = draft.data.file;
- }
- },
- methods: {
- async onPaste(e: ClipboardEvent) {
- const data = e.clipboardData;
- const items = data.items;
-
- if (items.length == 1) {
- if (items[0].kind == 'file') {
- const file = items[0].getAsFile();
- const lio = file.name.lastIndexOf('.');
- const ext = lio >= 0 ? file.name.slice(lio) : '';
- const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.pastedFileName).replace(/{{number}}/g, '1')}${ext}`;
- const name = this.$store.state.pasteDialog
- ? await os.dialog({
- title: this.$ts.enterFileName,
- input: {
- default: formatted
- },
- allowEmpty: false
- }).then(({ canceled, result }) => canceled ? false : result)
- : formatted;
- if (name) this.upload(file, name);
- }
- } else {
- if (items[0].kind == 'file') {
- os.dialog({
- type: 'error',
- text: this.$ts.onlyOneFileCanBeAttached
- });
- }
- }
- },
-
- onDragover(e) {
- const isFile = e.dataTransfer.items[0].kind == 'file';
- const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
- if (isFile || isDriveFile) {
- e.preventDefault();
- e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
- }
- },
-
- onDrop(e): void {
- // ใƒ•ใ‚กใ‚คใƒซใ ใฃใŸใ‚‰
- if (e.dataTransfer.files.length == 1) {
- e.preventDefault();
- this.upload(e.dataTransfer.files[0]);
- return;
- } else if (e.dataTransfer.files.length > 1) {
- e.preventDefault();
- os.dialog({
- type: 'error',
- text: this.$ts.onlyOneFileCanBeAttached
- });
- return;
- }
-
- //#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚กใ‚คใƒซ
- const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
- if (driveFile != null && driveFile != '') {
- this.file = JSON.parse(driveFile);
- e.preventDefault();
- }
- //#endregion
- },
-
- onKeypress(e) {
- this.typing();
- if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) {
- this.send();
- }
- },
-
- onCompositionUpdate() {
- this.typing();
- },
-
- chooseFile(e) {
- selectFile(e.currentTarget || e.target, this.$ts.selectFile, false).then(file => {
- this.file = file;
- });
- },
-
- onChangeFile() {
- this.upload((this.$refs.file as any).files[0]);
- },
-
- upload(file: File, name?: string) {
- os.upload(file, this.$store.state.uploadFolder, name).then(res => {
- this.file = res;
- });
- },
-
- send() {
- this.sending = true;
- os.api('messaging/messages/create', {
- userId: this.user ? this.user.id : undefined,
- groupId: this.group ? this.group.id : undefined,
- text: this.text ? this.text : undefined,
- fileId: this.file ? this.file.id : undefined
- }).then(message => {
- this.clear();
- }).catch(err => {
- console.error(err);
- }).then(() => {
- this.sending = false;
- });
- },
-
- clear() {
- this.text = '';
- this.file = null;
- this.deleteDraft();
- },
-
- saveDraft() {
- const data = JSON.parse(localStorage.getItem('message_drafts') || '{}');
-
- data[this.draftKey] = {
- updatedAt: new Date(),
- data: {
- text: this.text,
- file: this.file
- }
- }
-
- localStorage.setItem('message_drafts', JSON.stringify(data));
- },
-
- deleteDraft() {
- const data = JSON.parse(localStorage.getItem('message_drafts') || '{}');
-
- delete data[this.draftKey];
-
- localStorage.setItem('message_drafts', JSON.stringify(data));
- },
-
- async insertEmoji(ev) {
- os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.pemppnzi {
- position: relative;
-
- > textarea {
- cursor: auto;
- display: block;
- width: 100%;
- min-width: 100%;
- max-width: 100%;
- height: 80px;
- margin: 0;
- padding: 16px 16px 0 16px;
- resize: none;
- font-size: 1em;
- font-family: inherit;
- outline: none;
- border: none;
- border-radius: 0;
- box-shadow: none;
- background: transparent;
- box-sizing: border-box;
- color: var(--fg);
- }
-
- > .file {
- padding: 8px;
- color: #444;
- background: #eee;
- cursor: pointer;
- }
-
- > .send {
- position: absolute;
- bottom: 0;
- right: 0;
- margin: 0;
- padding: 16px;
- font-size: 1em;
- transition: color 0.1s ease;
- color: var(--accent);
-
- &:active {
- color: var(--accentDarken);
- transition: color 0s ease;
- }
- }
-
- .files {
- display: block;
- margin: 0;
- padding: 0 8px;
- list-style: none;
-
- &:after {
- content: '';
- display: block;
- clear: both;
- }
-
- > li {
- display: block;
- float: left;
- margin: 4px;
- padding: 0;
- width: 64px;
- height: 64px;
- background-color: #eee;
- background-repeat: no-repeat;
- background-position: center center;
- background-size: cover;
- cursor: move;
-
- &:hover {
- > .remove {
- display: block;
- }
- }
-
- > .remove {
- display: none;
- position: absolute;
- right: -6px;
- top: -6px;
- margin: 0;
- padding: 0;
- background: transparent;
- outline: none;
- border: none;
- border-radius: 0;
- box-shadow: none;
- cursor: pointer;
- }
- }
- }
-
- ._button {
- margin: 0;
- padding: 16px;
- font-size: 1em;
- font-weight: normal;
- text-decoration: none;
- transition: color 0.1s ease;
-
- &:hover {
- color: var(--accent);
- }
-
- &:active {
- color: var(--accentDarken);
- transition: color 0s ease;
- }
- }
-
- input[type=file] {
- display: none;
- }
-}
-</style>
diff --git a/src/client/pages/messaging/messaging-room.message.vue b/src/client/pages/messaging/messaging-room.message.vue
deleted file mode 100644
index a2740c0bdc..0000000000
--- a/src/client/pages/messaging/messaging-room.message.vue
+++ /dev/null
@@ -1,350 +0,0 @@
-<template>
-<div class="thvuemwp" :class="{ isMe }" v-size="{ max: [400, 500] }">
- <MkAvatar class="avatar" :user="message.user" :show-indicator="true"/>
- <div class="content">
- <div class="balloon" :class="{ noText: message.text == null }" >
- <button class="delete-button" v-if="isMe" :title="$ts.delete" @click="del">
- <img src="/static-assets/client/remove.png" alt="Delete"/>
- </button>
- <div class="content" v-if="!message.isDeleted">
- <Mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$i"/>
- <div class="file" v-if="message.file">
- <a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name">
- <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>
- <p v-else>{{ message.file.name }}</p>
- </a>
- </div>
- </div>
- <div class="content" v-else>
- <p class="is-deleted">{{ $ts.deleted }}</p>
- </div>
- </div>
- <div></div>
- <MkUrlPreview v-for="url in urls" :url="url" :key="url" style="margin: 8px 0;"/>
- <footer>
- <template v-if="isGroup">
- <span class="read" v-if="message.reads.length > 0">{{ $ts.messageRead }} {{ message.reads.length }}</span>
- </template>
- <template v-else>
- <span class="read" v-if="isMe && message.isRead">{{ $ts.messageRead }}</span>
- </template>
- <MkTime :time="message.createdAt"/>
- <template v-if="message.is_edited"><i class="fas fa-pencil-alt"></i></template>
- </footer>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as mfm from 'mfm-js';
-import { extractUrlFromMfm } from '@/misc/extract-url-from-mfm';
-import MkUrlPreview from '@client/components/url-preview.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkUrlPreview
- },
- props: {
- message: {
- required: true
- },
- isGroup: {
- required: false
- }
- },
- computed: {
- isMe(): boolean {
- return this.message.userId === this.$i.id;
- },
- urls(): string[] {
- if (this.message.text) {
- return extractUrlFromMfm(mfm.parse(this.message.text));
- } else {
- return [];
- }
- }
- },
- methods: {
- del() {
- os.api('messaging/messages/delete', {
- messageId: this.message.id
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.thvuemwp {
- $me-balloon-color: var(--accent);
-
- position: relative;
- background-color: transparent;
- display: flex;
-
- > .avatar {
- position: sticky;
- top: calc(var(--stickyTop, 0px) + 16px);
- display: block;
- width: 54px;
- height: 54px;
- transition: all 0.1s ease;
- }
-
- > .content {
- min-width: 0;
-
- > .balloon {
- position: relative;
- display: inline-flex;
- align-items: center;
- padding: 0;
- min-height: 38px;
- border-radius: 16px;
- max-width: 100%;
-
- &:before {
- content: "";
- pointer-events: none;
- display: block;
- position: absolute;
- top: 12px;
- }
-
- & + * {
- clear: both;
- }
-
- &:hover {
- > .delete-button {
- display: block;
- }
- }
-
- > .delete-button {
- display: none;
- position: absolute;
- z-index: 1;
- top: -4px;
- right: -4px;
- margin: 0;
- padding: 0;
- cursor: pointer;
- outline: none;
- border: none;
- border-radius: 0;
- box-shadow: none;
- background: transparent;
-
- > img {
- vertical-align: bottom;
- width: 16px;
- height: 16px;
- cursor: pointer;
- }
- }
-
- > .content {
- max-width: 100%;
-
- > .is-deleted {
- display: block;
- margin: 0;
- padding: 0;
- overflow: hidden;
- overflow-wrap: break-word;
- font-size: 1em;
- color: rgba(#000, 0.5);
- }
-
- > .text {
- display: block;
- margin: 0;
- padding: 12px 18px;
- overflow: hidden;
- overflow-wrap: break-word;
- word-break: break-word;
- font-size: 1em;
- color: rgba(#000, 0.8);
-
- & + .file {
- > a {
- border-radius: 0 0 16px 16px;
- }
- }
- }
-
- > .file {
- > a {
- display: block;
- max-width: 100%;
- border-radius: 16px;
- overflow: hidden;
- text-decoration: none;
-
- &:hover {
- text-decoration: none;
-
- > p {
- background: #ccc;
- }
- }
-
- > * {
- display: block;
- margin: 0;
- width: 100%;
- max-height: 512px;
- object-fit: contain;
- box-sizing: border-box;
- }
-
- > p {
- padding: 30px;
- text-align: center;
- color: #555;
- background: #ddd;
- }
- }
- }
- }
- }
-
- > footer {
- display: block;
- margin: 2px 0 0 0;
- font-size: 0.65em;
-
- > .read {
- margin: 0 8px;
- }
-
- > i {
- margin-left: 4px;
- }
- }
- }
-
- &:not(.isMe) {
- padding-left: var(--margin);
-
- > .content {
- padding-left: 16px;
- padding-right: 32px;
-
- > .balloon {
- $color: var(--messageBg);
- background: $color;
-
- &.noText {
- background: transparent;
- }
-
- &:not(.noText):before {
- left: -14px;
- border-top: solid 8px transparent;
- border-right: solid 8px $color;
- border-bottom: solid 8px transparent;
- border-left: solid 8px transparent;
- }
-
- > .content {
- > .text {
- color: var(--fg);
- }
- }
- }
-
- > footer {
- text-align: left;
- }
- }
- }
-
- &.isMe {
- flex-direction: row-reverse;
- padding-right: var(--margin);
-
- > .content {
- padding-right: 16px;
- padding-left: 32px;
- text-align: right;
-
- > .balloon {
- background: $me-balloon-color;
- text-align: left;
-
- ::selection {
- color: var(--accent);
- background-color: #fff;
- }
-
- &.noText {
- background: transparent;
- }
-
- &:not(.noText):before {
- right: -14px;
- left: auto;
- border-top: solid 8px transparent;
- border-right: solid 8px transparent;
- border-bottom: solid 8px transparent;
- border-left: solid 8px $me-balloon-color;
- }
-
- > .content {
-
- > p.is-deleted {
- color: rgba(#fff, 0.5);
- }
-
- > .text {
- &, ::v-deep(*) {
- color: var(--fgOnAccent) !important;
- }
- }
- }
- }
-
- > footer {
- text-align: right;
-
- > .read {
- user-select: none;
- }
- }
- }
- }
-
- &.max-width_400px {
- > .avatar {
- width: 48px;
- height: 48px;
- }
-
- > .content {
- > .balloon {
- > .content {
- > .text {
- font-size: 0.9em;
- }
- }
- }
- }
- }
-
- &.max-width_500px {
- > .content {
- > .balloon {
- > .content {
- > .text {
- padding: 8px 16px;
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/messaging/messaging-room.vue b/src/client/pages/messaging/messaging-room.vue
deleted file mode 100644
index 76e58d5bc9..0000000000
--- a/src/client/pages/messaging/messaging-room.vue
+++ /dev/null
@@ -1,470 +0,0 @@
-<template>
-<div class="_section"
- @dragover.prevent.stop="onDragover"
- @drop.prevent.stop="onDrop"
->
- <div class="_content mk-messaging-room">
- <div class="body">
- <MkLoading v-if="fetching"/>
- <p class="empty" v-if="!fetching && messages.length == 0"><i class="fas fa-info-circle"></i>{{ $ts.noMessagesYet }}</p>
- <p class="no-history" v-if="!fetching && messages.length > 0 && !existMoreMessages"><i class="fas fa-flag"></i>{{ $ts.noMoreHistory }}</p>
- <button class="more _button" ref="loadMore" :class="{ fetching: fetchingMoreMessages }" v-show="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
- <template v-if="fetchingMoreMessages"><i class="fas fa-spinner fa-pulse fa-fw"></i></template>{{ fetchingMoreMessages ? $ts.loading : $ts.loadMore }}
- </button>
- <XList class="messages" :items="messages" v-slot="{ item: message }" direction="up" reversed>
- <XMessage :message="message" :is-group="group != null" :key="message.id"/>
- </XList>
- </div>
- <footer>
- <div class="typers" v-if="typers.length > 0">
- <I18n :src="$ts.typingUsers" text-tag="span" class="users">
- <template #users>
- <b v-for="user in typers" :key="user.id" class="user">{{ user.username }}</b>
- </template>
- </I18n>
- <MkEllipsis/>
- </div>
- <transition name="fade">
- <div class="new-message" v-show="showIndicator">
- <button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas fa-arrow-circle-down"></i>{{ $ts.newMessageExists }}</button>
- </div>
- </transition>
- <XForm v-if="!fetching" :user="user" :group="group" ref="form" class="form"/>
- </footer>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, markRaw } from 'vue';
-import XList from '@client/components/date-separated-list.vue';
-import XMessage from './messaging-room.message.vue';
-import XForm from './messaging-room.form.vue';
-import { parseAcct } from '@/misc/acct';
-import { isBottom, onScrollBottom, scroll } from '@client/scripts/scroll';
-import * as os from '@client/os';
-import { popout } from '@client/scripts/popout';
-import * as sound from '@client/scripts/sound';
-import * as symbols from '@client/symbols';
-
-const Component = defineComponent({
- components: {
- XMessage,
- XForm,
- XList,
- },
-
- inject: ['inWindow'],
-
- props: {
- userAcct: {
- type: String,
- required: false,
- },
- groupId: {
- type: String,
- required: false,
- },
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => !this.fetching ? this.user ? {
- userName: this.user,
- avatar: this.user,
- action: {
- icon: 'fas fa-ellipsis-h',
- handler: this.menu,
- },
- } : {
- title: this.group.name,
- icon: 'fas fa-users',
- action: {
- icon: 'fas fa-ellipsis-h',
- handler: this.menu,
- },
- } : null),
- fetching: true,
- user: null,
- group: null,
- fetchingMoreMessages: false,
- messages: [],
- existMoreMessages: false,
- connection: null,
- showIndicator: false,
- timer: null,
- typers: [],
- ilObserver: new IntersectionObserver(
- (entries) => entries.some((entry) => entry.isIntersecting)
- && !this.fetching
- && !this.fetchingMoreMessages
- && this.existMoreMessages
- && this.fetchMoreMessages()
- ),
- };
- },
-
- computed: {
- form(): any {
- return this.$refs.form;
- }
- },
-
- watch: {
- userAcct: 'fetch',
- groupId: 'fetch',
- },
-
- mounted() {
- this.fetch();
- if (this.$store.state.enableInfiniteScroll) {
- this.$nextTick(() => this.ilObserver.observe(this.$refs.loadMore as Element));
- }
- },
-
- beforeUnmount() {
- this.connection.dispose();
-
- document.removeEventListener('visibilitychange', this.onVisibilitychange);
-
- this.ilObserver.disconnect();
- },
-
- methods: {
- async fetch() {
- this.fetching = true;
- if (this.userAcct) {
- const user = await os.api('users/show', parseAcct(this.userAcct));
- this.user = user;
- } else {
- const group = await os.api('users/groups/show', { groupId: this.groupId });
- this.group = group;
- }
-
- this.connection = markRaw(os.stream.useChannel('messaging', {
- otherparty: this.user ? this.user.id : undefined,
- group: this.group ? this.group.id : undefined,
- }));
-
- this.connection.on('message', this.onMessage);
- this.connection.on('read', this.onRead);
- this.connection.on('deleted', this.onDeleted);
- this.connection.on('typers', typers => {
- this.typers = typers.filter(u => u.id !== this.$i.id);
- });
-
- document.addEventListener('visibilitychange', this.onVisibilitychange);
-
- this.fetchMessages().then(() => {
- this.scrollToBottom();
-
- // ใ‚‚ใฃใจ่ฆ‹ใ‚‹ใฎไบคๅทฎๆคœ็Ÿฅใ‚’็™บ็ซใ•ใ›ใชใ„ใŸใ‚ใซfetchใฏ
- // ใ‚นใ‚ฏใƒญใƒผใƒซใŒ็ต‚ใ‚ใ‚‹ใพใงfalseใซใ—ใฆใŠใ
- // scrollendใฎใ‚ˆใ†ใชใ‚คใƒ™ใƒณใƒˆใฏใชใ„ใฎใงsetTimeoutใง
- setTimeout(() => this.fetching = false, 300);
- });
- },
-
- onDragover(e) {
- const isFile = e.dataTransfer.items[0].kind == 'file';
- const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
-
- if (isFile || isDriveFile) {
- e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
- } else {
- e.dataTransfer.dropEffect = 'none';
- }
- },
-
- onDrop(e): void {
- // ใƒ•ใ‚กใ‚คใƒซใ ใฃใŸใ‚‰
- if (e.dataTransfer.files.length == 1) {
- this.form.upload(e.dataTransfer.files[0]);
- return;
- } else if (e.dataTransfer.files.length > 1) {
- os.dialog({
- type: 'error',
- text: this.$ts.onlyOneFileCanBeAttached
- });
- return;
- }
-
- //#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚กใ‚คใƒซ
- const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
- if (driveFile != null && driveFile != '') {
- const file = JSON.parse(driveFile);
- this.form.file = file;
- }
- //#endregion
- },
-
- fetchMessages() {
- return new Promise((resolve, reject) => {
- const max = this.existMoreMessages ? 20 : 10;
-
- os.api('messaging/messages', {
- userId: this.user ? this.user.id : undefined,
- groupId: this.group ? this.group.id : undefined,
- limit: max + 1,
- untilId: this.existMoreMessages ? this.messages[0].id : undefined
- }).then(messages => {
- if (messages.length == max + 1) {
- this.existMoreMessages = true;
- messages.pop();
- } else {
- this.existMoreMessages = false;
- }
-
- this.messages.unshift.apply(this.messages, messages.reverse());
- resolve();
- });
- });
- },
-
- fetchMoreMessages() {
- this.fetchingMoreMessages = true;
- this.fetchMessages().then(() => {
- this.fetchingMoreMessages = false;
- });
- },
-
- onMessage(message) {
- sound.play('chat');
-
- const _isBottom = isBottom(this.$el, 64);
-
- this.messages.push(message);
- if (message.userId != this.$i.id && !document.hidden) {
- this.connection.send('read', {
- id: message.id
- });
- }
-
- if (_isBottom) {
- // Scroll to bottom
- this.$nextTick(() => {
- this.scrollToBottom();
- });
- } else if (message.userId != this.$i.id) {
- // Notify
- this.notifyNewMessage();
- }
- },
-
- onRead(x) {
- if (this.user) {
- if (!Array.isArray(x)) x = [x];
- for (const id of x) {
- if (this.messages.some(x => x.id == id)) {
- const exist = this.messages.map(x => x.id).indexOf(id);
- this.messages[exist] = {
- ...this.messages[exist],
- isRead: true,
- };
- }
- }
- } else if (this.group) {
- for (const id of x.ids) {
- if (this.messages.some(x => x.id == id)) {
- const exist = this.messages.map(x => x.id).indexOf(id);
- this.messages[exist] = {
- ...this.messages[exist],
- reads: [...this.messages[exist].reads, x.userId]
- };
- }
- }
- }
- },
-
- onDeleted(id) {
- const msg = this.messages.find(m => m.id === id);
- if (msg) {
- this.messages = this.messages.filter(m => m.id !== msg.id);
- }
- },
-
- scrollToBottom() {
- scroll(this.$el, { top: this.$el.offsetHeight });
- },
-
- onIndicatorClick() {
- this.showIndicator = false;
- this.scrollToBottom();
- },
-
- notifyNewMessage() {
- this.showIndicator = true;
-
- onScrollBottom(this.$el, () => {
- this.showIndicator = false;
- });
-
- if (this.timer) clearTimeout(this.timer);
-
- this.timer = setTimeout(() => {
- this.showIndicator = false;
- }, 4000);
- },
-
- onVisibilitychange() {
- if (document.hidden) return;
- for (const message of this.messages) {
- if (message.userId !== this.$i.id && !message.isRead) {
- this.connection.send('read', {
- id: message.id
- });
- }
- }
- },
-
- menu(ev) {
- const path = this.groupId ? `/my/messaging/group/${this.groupId}` : `/my/messaging/${this.userAcct}`;
-
- os.popupMenu([this.inWindow ? undefined : {
- text: this.$ts.openInWindow,
- icon: 'fas fa-window-maximize',
- action: () => {
- os.pageWindow(path);
- this.$router.back();
- },
- }, this.inWindow ? undefined : {
- text: this.$ts.popout,
- icon: 'fas fa-external-link-alt',
- action: () => {
- popout(path);
- this.$router.back();
- },
- }], ev.currentTarget || ev.target);
- }
- }
-});
-
-export default Component;
-</script>
-
-<style lang="scss" scoped>
-.mk-messaging-room {
- > .body {
- > .empty {
- width: 100%;
- margin: 0;
- padding: 16px 8px 8px 8px;
- text-align: center;
- font-size: 0.8em;
- opacity: 0.5;
-
- i {
- margin-right: 4px;
- }
- }
-
- > .no-history {
- display: block;
- margin: 0;
- padding: 16px;
- text-align: center;
- font-size: 0.8em;
- color: var(--messagingRoomInfo);
- opacity: 0.5;
-
- i {
- margin-right: 4px;
- }
- }
-
- > .more {
- display: block;
- margin: 16px auto;
- padding: 0 12px;
- line-height: 24px;
- color: #fff;
- background: rgba(#000, 0.3);
- border-radius: 12px;
-
- &:hover {
- background: rgba(#000, 0.4);
- }
-
- &:active {
- background: rgba(#000, 0.5);
- }
-
- &.fetching {
- cursor: wait;
- }
-
- > i {
- margin-right: 4px;
- }
- }
-
- > .messages {
- > ::v-deep(*) {
- margin-bottom: 16px;
- }
- }
- }
-
- > footer {
- width: 100%;
- position: relative;
-
- > .new-message {
- position: absolute;
- top: -48px;
- width: 100%;
- padding: 8px 0;
- text-align: center;
-
- > button {
- display: inline-block;
- margin: 0;
- padding: 0 12px 0 30px;
- line-height: 32px;
- font-size: 12px;
- border-radius: 16px;
-
- > i {
- position: absolute;
- top: 0;
- left: 10px;
- line-height: 32px;
- font-size: 16px;
- }
- }
- }
-
- > .typers {
- position: absolute;
- bottom: 100%;
- padding: 0 8px 0 8px;
- font-size: 0.9em;
- color: var(--fgTransparentWeak);
-
- > .users {
- > .user + .user:before {
- content: ", ";
- font-weight: normal;
- }
-
- > .user:last-of-type:after {
- content: " ";
- }
- }
- }
-
- > .form {
- border-top: solid 0.5px var(--divider);
- }
- }
-}
-
-.fade-enter-active, .fade-leave-active {
- transition: opacity 0.1s;
-}
-
-.fade-enter-from, .fade-leave-to {
- transition: opacity 0.5s;
- opacity: 0;
-}
-</style>
diff --git a/src/client/pages/mfm-cheat-sheet.vue b/src/client/pages/mfm-cheat-sheet.vue
deleted file mode 100644
index 5ff4317627..0000000000
--- a/src/client/pages/mfm-cheat-sheet.vue
+++ /dev/null
@@ -1,365 +0,0 @@
-<template>
-<div class="mwysmxbg">
- <div class="_isolated">{{ $ts._mfm.intro }}</div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.mention }}</div>
- <div class="content">
- <p>{{ $ts._mfm.mentionDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_mention"/>
- <MkTextarea v-model="preview_mention"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.hashtag }}</div>
- <div class="content">
- <p>{{ $ts._mfm.hashtagDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_hashtag"/>
- <MkTextarea v-model="preview_hashtag"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.url }}</div>
- <div class="content">
- <p>{{ $ts._mfm.urlDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_url"/>
- <MkTextarea v-model="preview_url"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.link }}</div>
- <div class="content">
- <p>{{ $ts._mfm.linkDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_link"/>
- <MkTextarea v-model="preview_link"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.emoji }}</div>
- <div class="content">
- <p>{{ $ts._mfm.emojiDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_emoji"/>
- <MkTextarea v-model="preview_emoji"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.bold }}</div>
- <div class="content">
- <p>{{ $ts._mfm.boldDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_bold"/>
- <MkTextarea v-model="preview_bold"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.small }}</div>
- <div class="content">
- <p>{{ $ts._mfm.smallDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_small"/>
- <MkTextarea v-model="preview_small"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.quote }}</div>
- <div class="content">
- <p>{{ $ts._mfm.quoteDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_quote"/>
- <MkTextarea v-model="preview_quote"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.center }}</div>
- <div class="content">
- <p>{{ $ts._mfm.centerDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_center"/>
- <MkTextarea v-model="preview_center"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.inlineCode }}</div>
- <div class="content">
- <p>{{ $ts._mfm.inlineCodeDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_inlineCode"/>
- <MkTextarea v-model="preview_inlineCode"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.blockCode }}</div>
- <div class="content">
- <p>{{ $ts._mfm.blockCodeDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_blockCode"/>
- <MkTextarea v-model="preview_blockCode"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.inlineMath }}</div>
- <div class="content">
- <p>{{ $ts._mfm.inlineMathDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_inlineMath"/>
- <MkTextarea v-model="preview_inlineMath"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.search }}</div>
- <div class="content">
- <p>{{ $ts._mfm.searchDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_search"/>
- <MkTextarea v-model="preview_search"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.flip }}</div>
- <div class="content">
- <p>{{ $ts._mfm.flipDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_flip"/>
- <MkTextarea v-model="preview_flip"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.font }}</div>
- <div class="content">
- <p>{{ $ts._mfm.fontDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_font"/>
- <MkTextarea v-model="preview_font"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.x2 }}</div>
- <div class="content">
- <p>{{ $ts._mfm.x2Description }}</p>
- <div class="preview">
- <Mfm :text="preview_x2"/>
- <MkTextarea v-model="preview_x2"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.x3 }}</div>
- <div class="content">
- <p>{{ $ts._mfm.x3Description }}</p>
- <div class="preview">
- <Mfm :text="preview_x3"/>
- <MkTextarea v-model="preview_x3"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.x4 }}</div>
- <div class="content">
- <p>{{ $ts._mfm.x4Description }}</p>
- <div class="preview">
- <Mfm :text="preview_x4"/>
- <MkTextarea v-model="preview_x4"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.blur }}</div>
- <div class="content">
- <p>{{ $ts._mfm.blurDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_blur"/>
- <MkTextarea v-model="preview_blur"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.jelly }}</div>
- <div class="content">
- <p>{{ $ts._mfm.jellyDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_jelly"/>
- <MkTextarea v-model="preview_jelly"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.tada }}</div>
- <div class="content">
- <p>{{ $ts._mfm.tadaDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_tada"/>
- <MkTextarea v-model="preview_tada"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.jump }}</div>
- <div class="content">
- <p>{{ $ts._mfm.jumpDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_jump"/>
- <MkTextarea v-model="preview_jump"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.bounce }}</div>
- <div class="content">
- <p>{{ $ts._mfm.bounceDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_bounce"/>
- <MkTextarea v-model="preview_bounce"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.spin }}</div>
- <div class="content">
- <p>{{ $ts._mfm.spinDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_spin"/>
- <MkTextarea v-model="preview_spin"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.shake }}</div>
- <div class="content">
- <p>{{ $ts._mfm.shakeDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_shake"/>
- <MkTextarea v-model="preview_shake"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.twitch }}</div>
- <div class="content">
- <p>{{ $ts._mfm.twitchDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_twitch"/>
- <MkTextarea v-model="preview_twitch"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.rainbow }}</div>
- <div class="content">
- <p>{{ $ts._mfm.rainbowDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_rainbow"/>
- <MkTextarea v-model="preview_rainbow"><template #label>MFM</template></MkTextarea>
- </div>
- </div>
- </div>
- <div class="section _block">
- <div class="title">{{ $ts._mfm.sparkle }}</div>
- <div class="content">
- <p>{{ $ts._mfm.sparkleDescription }}</p>
- <div class="preview">
- <Mfm :text="preview_sparkle"/>
- <MkTextarea v-model="preview_sparkle"><span>MFM</span></MkTextarea>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkTextarea
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts._mfm.cheatSheet,
- icon: 'fas fa-question-circle',
- },
- preview_mention: '@example',
- preview_hashtag: '#test',
- preview_url: `https://example.com`,
- preview_link: `[${this.$ts._mfm.dummy}](https://example.com)`,
- preview_emoji: this.$instance.emojis.length ? `:${this.$instance.emojis[0].name}:` : `:emojiname:`,
- preview_bold: `**${this.$ts._mfm.dummy}**`,
- preview_small: `<small>${this.$ts._mfm.dummy}</small>`,
- preview_center: `<center>${this.$ts._mfm.dummy}</center>`,
- preview_inlineCode: '`<: "Hello, world!"`',
- preview_blockCode: '```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```',
- preview_inlineMath: '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)',
- preview_quote: `> ${this.$ts._mfm.dummy}`,
- preview_search: `${this.$ts._mfm.dummy} ๆคœ็ดข`,
- preview_jelly: `$[jelly ๐Ÿฎ]`,
- preview_tada: `$[tada ๐Ÿฎ]`,
- preview_jump: `$[jump ๐Ÿฎ]`,
- preview_bounce: `$[bounce ๐Ÿฎ]`,
- preview_shake: `$[shake ๐Ÿฎ]`,
- preview_twitch: `$[twitch ๐Ÿฎ]`,
- preview_spin: `$[spin ๐Ÿฎ] $[spin.left ๐Ÿฎ] $[spin.alternate ๐Ÿฎ]\n$[spin.x ๐Ÿฎ] $[spin.x,left ๐Ÿฎ] $[spin.x,alternate ๐Ÿฎ]\n$[spin.y ๐Ÿฎ] $[spin.y,left ๐Ÿฎ] $[spin.y,alternate ๐Ÿฎ]`,
- preview_flip: `$[flip ${this.$ts._mfm.dummy}]\n$[flip.v ${this.$ts._mfm.dummy}]\n$[flip.h,v ${this.$ts._mfm.dummy}]`,
- preview_font: `$[font.serif ${this.$ts._mfm.dummy}]\n$[font.monospace ${this.$ts._mfm.dummy}]\n$[font.cursive ${this.$ts._mfm.dummy}]\n$[font.fantasy ${this.$ts._mfm.dummy}]`,
- preview_x2: `$[x2 ๐Ÿฎ]`,
- preview_x3: `$[x3 ๐Ÿฎ]`,
- preview_x4: `$[x4 ๐Ÿฎ]`,
- preview_blur: `$[blur ${this.$ts._mfm.dummy}]`,
- preview_rainbow: `$[rainbow ๐Ÿฎ]`,
- preview_sparkle: `$[sparkle ๐Ÿฎ]`,
- }
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.mwysmxbg {
- background: var(--bg);
-
- > .section {
- > .title {
- position: sticky;
- z-index: 1;
- top: var(--stickyTop, 0px);
- padding: 16px;
- font-weight: bold;
- -webkit-backdrop-filter: var(--blur, blur(10px));
- backdrop-filter: var(--blur, blur(10px));
- background-color: var(--X16);
- }
-
- > .content {
- > p {
- margin: 0;
- padding: 16px;
- }
-
- > .preview {
- border-top: solid 0.5px var(--divider);
- padding: 16px;
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/miauth.vue b/src/client/pages/miauth.vue
deleted file mode 100644
index 39cd832838..0000000000
--- a/src/client/pages/miauth.vue
+++ /dev/null
@@ -1,100 +0,0 @@
-<template>
-<div v-if="$i">
- <div class="waiting _section" v-if="state == 'waiting'">
- <div class="_content">
- <MkLoading/>
- </div>
- </div>
- <div class="denied _section" v-if="state == 'denied'">
- <div class="_content">
- <p>{{ $ts._auth.denied }}</p>
- </div>
- </div>
- <div class="accepted _section" v-else-if="state == 'accepted'">
- <div class="_content">
- <p v-if="callback">{{ $ts._auth.callback }}<MkEllipsis/></p>
- <p v-else>{{ $ts._auth.pleaseGoBack }}</p>
- </div>
- </div>
- <div class="_section" v-else>
- <div class="_title" v-if="name">{{ $t('_auth.shareAccess', { name: name }) }}</div>
- <div class="_title" v-else>{{ $ts._auth.shareAccessAsk }}</div>
- <div class="_content">
- <p>{{ $ts._auth.permissionAsk }}</p>
- <ul>
- <li v-for="p in permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
- </ul>
- </div>
- <div class="_footer">
- <MkButton @click="deny" inline>{{ $ts.cancel }}</MkButton>
- <MkButton @click="accept" inline primary>{{ $ts.accept }}</MkButton>
- </div>
- </div>
-</div>
-<div class="signin" v-else>
- <MkSignin @login="onLogin"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkSignin from '@client/components/signin.vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import { login } from '@client/account';
-
-export default defineComponent({
- components: {
- MkSignin,
- MkButton,
- },
- data() {
- return {
- state: null
- };
- },
- computed: {
- session(): string {
- return this.$route.params.session;
- },
- callback(): string {
- return this.$route.query.callback;
- },
- name(): string {
- return this.$route.query.name;
- },
- icon(): string {
- return this.$route.query.icon;
- },
- permission(): string[] {
- return this.$route.query.permission ? this.$route.query.permission.split(',') : [];
- },
- },
- methods: {
- async accept() {
- this.state = 'waiting';
- await os.api('miauth/gen-token', {
- session: this.session,
- name: this.name,
- iconUrl: this.icon,
- permission: this.permission,
- });
-
- this.state = 'accepted';
- if (this.callback) {
- location.href = `${this.callback}?session=${this.session}`;
- }
- },
- deny() {
- this.state = 'denied';
- },
- onLogin(res) {
- login(res.i);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/my-antennas/create.vue b/src/client/pages/my-antennas/create.vue
deleted file mode 100644
index d4762411e7..0000000000
--- a/src/client/pages/my-antennas/create.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<template>
-<div class="geegznzt">
- <XAntenna :antenna="draft" @created="onAntennaCreated"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import XAntenna from './editor.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- XAntenna,
- },
-
- 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
- },
- };
- },
-
- methods: {
- onAntennaCreated() {
- this.$router.push('/my/antennas');
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/my-antennas/edit.vue b/src/client/pages/my-antennas/edit.vue
deleted file mode 100644
index 9deafb4235..0000000000
--- a/src/client/pages/my-antennas/edit.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<template>
-<div class="">
- <XAntenna v-if="antenna" :antenna="antenna" @updated="onAntennaUpdated"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import XAntenna from './editor.vue';
-import * as symbols from '@client/symbols';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkButton,
- XAntenna,
- },
-
- props: {
- antennaId: {
- type: String,
- required: true,
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.manageAntennas,
- icon: 'fas fa-satellite',
- },
- antenna: null,
- };
- },
-
- watch: {
- antennaId: {
- async handler() {
- this.antenna = await os.api('antennas/show', { antennaId: this.antennaId });
- },
- immediate: true,
- }
- },
-
- methods: {
- onAntennaUpdated() {
- this.$router.push('/my/antennas');
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/my-antennas/editor.vue b/src/client/pages/my-antennas/editor.vue
deleted file mode 100644
index 93ab640030..0000000000
--- a/src/client/pages/my-antennas/editor.vue
+++ /dev/null
@@ -1,190 +0,0 @@
-<template>
-<div class="shaynizk">
- <div class="form">
- <MkInput v-model="name" class="_formBlock">
- <template #label>{{ $ts.name }}</template>
- </MkInput>
- <MkSelect v-model="src" class="_formBlock">
- <template #label>{{ $ts.antennaSource }}</template>
- <option value="all">{{ $ts._antennaSources.all }}</option>
- <option value="home">{{ $ts._antennaSources.homeTimeline }}</option>
- <option value="users">{{ $ts._antennaSources.users }}</option>
- <option value="list">{{ $ts._antennaSources.userList }}</option>
- <option value="group">{{ $ts._antennaSources.userGroup }}</option>
- </MkSelect>
- <MkSelect v-model="userListId" v-if="src === 'list'" class="_formBlock">
- <template #label>{{ $ts.userList }}</template>
- <option v-for="list in userLists" :value="list.id" :key="list.id">{{ list.name }}</option>
- </MkSelect>
- <MkSelect v-model="userGroupId" v-else-if="src === 'group'" class="_formBlock">
- <template #label>{{ $ts.userGroup }}</template>
- <option v-for="group in userGroups" :value="group.id" :key="group.id">{{ group.name }}</option>
- </MkSelect>
- <MkTextarea v-model="users" v-else-if="src === 'users'" class="_formBlock">
- <template #label>{{ $ts.users }}</template>
- <template #caption>{{ $ts.antennaUsersDescription }} <button class="_textButton" @click="addUser">{{ $ts.addUser }}</button></template>
- </MkTextarea>
- <MkSwitch v-model="withReplies" class="_formBlock">{{ $ts.withReplies }}</MkSwitch>
- <MkTextarea v-model="keywords" class="_formBlock">
- <template #label>{{ $ts.antennaKeywords }}</template>
- <template #caption>{{ $ts.antennaKeywordsDescription }}</template>
- </MkTextarea>
- <MkTextarea v-model="excludeKeywords" class="_formBlock">
- <template #label>{{ $ts.antennaExcludeKeywords }}</template>
- <template #caption>{{ $ts.antennaKeywordsDescription }}</template>
- </MkTextarea>
- <MkSwitch v-model="caseSensitive" class="_formBlock">{{ $ts.caseSensitive }}</MkSwitch>
- <MkSwitch v-model="withFile" class="_formBlock">{{ $ts.withFileAntenna }}</MkSwitch>
- <MkSwitch v-model="notify" class="_formBlock">{{ $ts.notifyAntenna }}</MkSwitch>
- </div>
- <div class="actions">
- <MkButton inline @click="saveAntenna()" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
- <MkButton inline @click="deleteAntenna()" v-if="antenna.id != null" danger><i class="fas fa-trash"></i> {{ $ts.delete }}</MkButton>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import { getAcct } from '@/misc/acct';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkButton, MkInput, MkTextarea, MkSelect, MkSwitch
- },
-
- props: {
- antenna: {
- type: Object,
- required: true
- }
- },
-
- data() {
- return {
- name: '',
- src: '',
- userListId: null,
- userGroupId: null,
- users: '',
- keywords: '',
- excludeKeywords: '',
- caseSensitive: false,
- withReplies: false,
- withFile: false,
- notify: false,
- userLists: null,
- userGroups: null,
- };
- },
-
- watch: {
- async src() {
- if (this.src === 'list' && this.userLists === null) {
- this.userLists = await os.api('users/lists/list');
- }
-
- if (this.src === 'group' && this.userGroups === null) {
- const groups1 = await os.api('users/groups/owned');
- const groups2 = await os.api('users/groups/joined');
-
- this.userGroups = [...groups1, ...groups2];
- }
- }
- },
-
- created() {
- this.name = this.antenna.name;
- this.src = this.antenna.src;
- this.userListId = this.antenna.userListId;
- this.userGroupId = this.antenna.userGroupId;
- this.users = this.antenna.users.join('\n');
- this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n');
- this.excludeKeywords = this.antenna.excludeKeywords.map(x => x.join(' ')).join('\n');
- this.caseSensitive = this.antenna.caseSensitive;
- this.withReplies = this.antenna.withReplies;
- this.withFile = this.antenna.withFile;
- this.notify = this.antenna.notify;
- },
-
- methods: {
- async saveAntenna() {
- if (this.antenna.id == null) {
- await os.apiWithDialog('antennas/create', {
- name: this.name,
- src: this.src,
- userListId: this.userListId,
- userGroupId: this.userGroupId,
- withReplies: this.withReplies,
- withFile: this.withFile,
- notify: this.notify,
- caseSensitive: this.caseSensitive,
- users: this.users.trim().split('\n').map(x => x.trim()),
- keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')),
- excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
- });
- this.$emit('created');
- } else {
- await os.apiWithDialog('antennas/update', {
- antennaId: this.antenna.id,
- name: this.name,
- src: this.src,
- userListId: this.userListId,
- userGroupId: this.userGroupId,
- withReplies: this.withReplies,
- withFile: this.withFile,
- notify: this.notify,
- caseSensitive: this.caseSensitive,
- users: this.users.trim().split('\n').map(x => x.trim()),
- keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')),
- excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
- });
- this.$emit('updated');
- }
- },
-
- async deleteAntenna() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: this.antenna.name }),
- showCancelButton: true
- });
- if (canceled) return;
-
- await os.api('antennas/delete', {
- antennaId: this.antenna.id,
- });
-
- os.success();
- this.$emit('deleted');
- },
-
- addUser() {
- os.selectUser().then(user => {
- this.users = this.users.trim();
- this.users += '\n@' + getAcct(user);
- this.users = this.users.trim();
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.shaynizk {
- > .form {
- padding: 32px;
- }
-
- > .actions {
- padding: 24px 32px;
- border-top: solid 0.5px var(--divider);
- }
-}
-</style>
diff --git a/src/client/pages/my-antennas/index.vue b/src/client/pages/my-antennas/index.vue
deleted file mode 100644
index c27bb2c15e..0000000000
--- a/src/client/pages/my-antennas/index.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<template>
-<div class="ieepwinx _section">
- <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
-
- <div class="_content">
- <MkPagination :pagination="pagination" #default="{items}" ref="list">
- <MkA class="ljoevbzj" v-for="antenna in items" :key="antenna.id" :to="`/my/antennas/${antenna.id}`">
- <div class="name">{{ antenna.name }}</div>
- </MkA>
- </MkPagination>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkButton,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.manageAntennas,
- icon: 'fas fa-satellite',
- action: {
- icon: 'fas fa-plus',
- handler: this.create
- }
- },
- pagination: {
- endpoint: 'antennas/list',
- limit: 10,
- },
- };
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.ieepwinx {
- padding: 16px;
-
- > .add {
- margin: 0 auto 16px auto;
- }
-
- .ljoevbzj {
- display: block;
- padding: 16px;
- margin-bottom: 8px;
- border: solid 1px var(--divider);
- border-radius: 6px;
-
- &:hover {
- border: solid 1px var(--accent);
- text-decoration: none;
- }
-
- > .name {
- font-weight: bold;
- }
- }
-}
-</style>
diff --git a/src/client/pages/my-clips/index.vue b/src/client/pages/my-clips/index.vue
deleted file mode 100644
index c4ca474748..0000000000
--- a/src/client/pages/my-clips/index.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<template>
-<div class="_section qtcaoidl">
- <MkButton @click="create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
-
- <div class="_content">
- <MkPagination :pagination="pagination" #default="{items}" ref="list" 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>
- </MkA>
- </MkPagination>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkButton,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.clip,
- icon: 'fas fa-paperclip',
- action: {
- icon: 'fas fa-plus',
- handler: this.create
- }
- },
- pagination: {
- endpoint: 'clips/list',
- limit: 10,
- },
- draft: null,
- };
- },
-
- 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;
-
- os.apiWithDialog('clips/create', result);
- },
-
- onClipCreated() {
- this.$refs.list.reload();
- this.draft = null;
- },
-
- onClipDeleted() {
- this.$refs.list.reload();
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.qtcaoidl {
- > .add {
- margin: 0 auto 16px auto;
- }
-
- > ._content {
- > .list {
- > .item {
- display: block;
- padding: 16px;
-
- > .description {
- margin-top: 8px;
- padding-top: 8px;
- border-top: solid 0.5px var(--divider);
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/my-groups/group.vue b/src/client/pages/my-groups/group.vue
deleted file mode 100644
index bd5537cbfa..0000000000
--- a/src/client/pages/my-groups/group.vue
+++ /dev/null
@@ -1,184 +0,0 @@
-<template>
-<div class="mk-group-page">
- <transition name="zoom" mode="out-in">
- <div v-if="group" class="_section">
- <div class="_content">
- <MkButton inline @click="invite()">{{ $ts.invite }}</MkButton>
- <MkButton inline @click="renameGroup()">{{ $ts.rename }}</MkButton>
- <MkButton inline @click="transfer()">{{ $ts.transfer }}</MkButton>
- <MkButton inline @click="deleteGroup()">{{ $ts.delete }}</MkButton>
- </div>
- </div>
- </transition>
-
- <transition name="zoom" mode="out-in">
- <div v-if="group" class="_section members _gap">
- <div class="_title">{{ $ts.members }}</div>
- <div class="_content">
- <div class="users">
- <div class="user _panel" v-for="user in users" :key="user.id">
- <MkAvatar :user="user" class="avatar" :show-indicator="true"/>
- <div class="body">
- <MkUserName :user="user" class="name"/>
- <MkAcct :user="user" class="acct"/>
- </div>
- <div class="action">
- <button class="_button" @click="removeUser(user)"><i class="fas fa-times"></i></button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </transition>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton
- },
-
- props: {
- groupId: {
- type: String,
- required: true,
- },
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.group ? {
- title: this.group.name,
- icon: 'fas fa-users',
- } : null),
- group: null,
- users: [],
- };
- },
-
- watch: {
- groupId: 'fetch',
- },
-
- created() {
- this.fetch();
- },
-
- methods: {
- fetch() {
- Progress.start();
- os.api('users/groups/show', {
- groupId: this.groupId
- }).then(group => {
- this.group = group;
- os.api('users/show', {
- userIds: this.group.userIds
- }).then(users => {
- this.users = users;
- Progress.done();
- });
- });
- },
-
- invite() {
- os.selectUser().then(user => {
- os.apiWithDialog('users/groups/invite', {
- groupId: this.group.id,
- userId: user.id
- });
- });
- },
-
- removeUser(user) {
- os.api('users/groups/pull', {
- groupId: this.group.id,
- userId: user.id
- }).then(() => {
- this.users = this.users.filter(x => x.id !== user.id);
- });
- },
-
- async renameGroup() {
- const { canceled, result: name } = await os.dialog({
- title: this.$ts.groupName,
- input: {
- default: this.group.name
- }
- });
- if (canceled) return;
-
- await os.api('users/groups/update', {
- groupId: this.group.id,
- name: name
- });
-
- this.group.name = name;
- },
-
- transfer() {
- os.selectUser().then(user => {
- os.apiWithDialog('users/groups/transfer', {
- groupId: this.group.id,
- userId: user.id
- });
- });
- },
-
- async deleteGroup() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: this.group.name }),
- showCancelButton: true
- });
- if (canceled) return;
-
- await os.apiWithDialog('users/groups/delete', {
- groupId: this.group.id
- });
- this.$router.push('/my/groups');
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-group-page {
- > .members {
- > ._content {
- > .users {
- > .user {
- display: flex;
- align-items: center;
- padding: 16px;
-
- > .avatar {
- width: 50px;
- height: 50px;
- }
-
- > .body {
- flex: 1;
- padding: 8px;
-
- > .name {
- display: block;
- font-weight: bold;
- }
-
- > .acct {
- opacity: 0.5;
- }
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/my-groups/index.vue b/src/client/pages/my-groups/index.vue
deleted file mode 100644
index 34f82f8a71..0000000000
--- a/src/client/pages/my-groups/index.vue
+++ /dev/null
@@ -1,121 +0,0 @@
-<template>
-<div class="">
- <div class="_section" style="padding: 0;">
- <MkTab v-model="tab">
- <option value="owned">{{ $ts.ownedGroups }}</option>
- <option value="joined">{{ $ts.joinedGroups }}</option>
- <option value="invites"><i class="fas fa-envelope-open-text"></i> {{ $ts.invites }}</option>
- </MkTab>
- </div>
-
- <div class="_section">
- <div class="_content" v-if="tab === 'owned'">
- <MkButton @click="create" primary style="margin: 0 auto var(--margin) auto;"><i class="fas fa-plus"></i> {{ $ts.createGroup }}</MkButton>
-
- <MkPagination :pagination="ownedPagination" #default="{items}" ref="owned">
- <div class="_card" v-for="group in items" :key="group.id">
- <div class="_title"><MkA :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</MkA></div>
- <div class="_content"><MkAvatars :user-ids="group.userIds"/></div>
- </div>
- </MkPagination>
- </div>
-
- <div class="_content" v-else-if="tab === 'joined'">
- <MkPagination :pagination="joinedPagination" #default="{items}" ref="joined">
- <div class="_card" v-for="group in items" :key="group.id">
- <div class="_title">{{ group.name }}</div>
- <div class="_content"><MkAvatars :user-ids="group.userIds"/></div>
- </div>
- </MkPagination>
- </div>
-
- <div class="_content" v-else-if="tab === 'invites'">
- <MkPagination :pagination="invitationPagination" #default="{items}" ref="invitations">
- <div class="_card" v-for="invitation in items" :key="invitation.id">
- <div class="_title">{{ invitation.group.name }}</div>
- <div class="_content"><MkAvatars :user-ids="invitation.group.userIds"/></div>
- <div class="_footer">
- <MkButton @click="acceptInvite(invitation)" primary inline><i class="fas fa-check"></i> {{ $ts.accept }}</MkButton>
- <MkButton @click="rejectInvite(invitation)" primary inline><i class="fas fa-ban"></i> {{ $ts.reject }}</MkButton>
- </div>
- </div>
- </MkPagination>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkAvatars from '@client/components/avatars.vue';
-import MkTab from '@client/components/tab.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkButton,
- MkContainer,
- MkTab,
- MkAvatars,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.groups,
- icon: 'fas fa-users'
- },
- tab: 'owned',
- ownedPagination: {
- endpoint: 'users/groups/owned',
- limit: 10,
- },
- joinedPagination: {
- endpoint: 'users/groups/joined',
- limit: 10,
- },
- invitationPagination: {
- endpoint: 'i/user-group-invites',
- limit: 10,
- },
- };
- },
-
- methods: {
- async create() {
- const { canceled, result: name } = await os.dialog({
- title: this.$ts.groupName,
- input: true
- });
- if (canceled) return;
- await os.api('users/groups/create', { name: name });
- this.$refs.owned.reload();
- os.success();
- },
- acceptInvite(invitation) {
- os.api('users/groups/invitations/accept', {
- invitationId: invitation.id
- }).then(() => {
- os.success();
- this.$refs.invitations.reload();
- this.$refs.joined.reload();
- });
- },
- rejectInvite(invitation) {
- os.api('users/groups/invitations/reject', {
- invitationId: invitation.id
- }).then(() => {
- this.$refs.invitations.reload();
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-</style>
diff --git a/src/client/pages/my-lists/index.vue b/src/client/pages/my-lists/index.vue
deleted file mode 100644
index 687e9e630e..0000000000
--- a/src/client/pages/my-lists/index.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-<template>
-<div class="qkcjvfiv">
- <MkButton @click="create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton>
-
- <MkPagination :pagination="pagination" #default="{items}" class="lists _content" ref="list">
- <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"/>
- </MkA>
- </MkPagination>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkAvatars from '@client/components/avatars.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkButton,
- MkAvatars,
- },
-
- 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,
- },
- };
- },
-
- methods: {
- async create() {
- const { canceled, result: name } = await os.dialog({
- title: this.$ts.enterListName,
- input: true
- });
- if (canceled) return;
- await os.api('users/lists/create', { name: name });
- this.$refs.list.reload();
- os.success();
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.qkcjvfiv {
- padding: 16px;
-
- > .add {
- margin: 0 auto var(--margin) auto;
- }
-
- > .lists {
- > .list {
- display: block;
- padding: 16px;
- border: solid 1px var(--divider);
- border-radius: 6px;
-
- &:hover {
- border: solid 1px var(--accent);
- text-decoration: none;
- }
-
- > .name {
- margin-bottom: 4px;
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/my-lists/list.vue b/src/client/pages/my-lists/list.vue
deleted file mode 100644
index 049d370b4e..0000000000
--- a/src/client/pages/my-lists/list.vue
+++ /dev/null
@@ -1,170 +0,0 @@
-<template>
-<div class="mk-list-page">
- <transition name="zoom" mode="out-in">
- <div v-if="list" class="_section">
- <div class="_content">
- <MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton>
- <MkButton inline @click="renameList()">{{ $ts.rename }}</MkButton>
- <MkButton inline @click="deleteList()">{{ $ts.delete }}</MkButton>
- </div>
- </div>
- </transition>
-
- <transition name="zoom" mode="out-in">
- <div v-if="list" class="_section members _gap">
- <div class="_title">{{ $ts.members }}</div>
- <div class="_content">
- <div class="users">
- <div class="user _panel" v-for="user in users" :key="user.id">
- <MkAvatar :user="user" class="avatar" :show-indicator="true"/>
- <div class="body">
- <MkUserName :user="user" class="name"/>
- <MkAcct :user="user" class="acct"/>
- </div>
- <div class="action">
- <button class="_button" @click="removeUser(user)"><i class="fas fa-times"></i></button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </transition>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.list ? {
- title: this.list.name,
- icon: 'fas fa-list-ul',
- } : null),
- list: null,
- users: [],
- };
- },
-
- watch: {
- $route: 'fetch'
- },
-
- created() {
- this.fetch();
- },
-
- methods: {
- fetch() {
- Progress.start();
- os.api('users/lists/show', {
- listId: this.$route.params.list
- }).then(list => {
- this.list = list;
- os.api('users/show', {
- userIds: this.list.userIds
- }).then(users => {
- this.users = users;
- Progress.done();
- });
- });
- },
-
- addUser() {
- os.selectUser().then(user => {
- os.apiWithDialog('users/lists/push', {
- listId: this.list.id,
- userId: user.id
- }).then(() => {
- this.users.push(user);
- });
- });
- },
-
- removeUser(user) {
- os.api('users/lists/pull', {
- listId: this.list.id,
- userId: user.id
- }).then(() => {
- this.users = this.users.filter(x => x.id !== user.id);
- });
- },
-
- async renameList() {
- const { canceled, result: name } = await os.dialog({
- title: this.$ts.enterListName,
- input: {
- default: this.list.name
- }
- });
- if (canceled) return;
-
- await os.api('users/lists/update', {
- listId: this.list.id,
- name: name
- });
-
- this.list.name = name;
- },
-
- async deleteList() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: this.list.name }),
- showCancelButton: true
- });
- if (canceled) return;
-
- await os.api('users/lists/delete', {
- listId: this.list.id
- });
- os.success();
- this.$router.push('/my/lists');
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-list-page {
- > .members {
- > ._content {
- > .users {
- > .user {
- display: flex;
- align-items: center;
- padding: 16px;
-
- > .avatar {
- width: 50px;
- height: 50px;
- }
-
- > .body {
- flex: 1;
- padding: 8px;
-
- > .name {
- display: block;
- font-weight: bold;
- }
-
- > .acct {
- opacity: 0.5;
- }
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/not-found.vue b/src/client/pages/not-found.vue
deleted file mode 100644
index 5e7fe17f75..0000000000
--- a/src/client/pages/not-found.vue
+++ /dev/null
@@ -1,25 +0,0 @@
-<template>
-<div class="ipledcug">
- <div class="_fullinfo">
- <img src="https://xn--931a.moe/assets/not-found.jpg" class="_ghost"/>
- <div>{{ $ts.notFoundDescription }}</div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.notFound,
- icon: 'fas fa-exclamation-triangle'
- },
- }
- },
-});
-</script>
diff --git a/src/client/pages/note.vue b/src/client/pages/note.vue
deleted file mode 100644
index 8e95430d67..0000000000
--- a/src/client/pages/note.vue
+++ /dev/null
@@ -1,209 +0,0 @@
-<template>
-<MkSpacer :content-max="800">
- <div class="fcuexfpr">
- <transition name="fade" mode="out-in">
- <div v-if="note" class="note">
- <div class="_gap" v-if="showNext">
- <XNotes class="_content" :pagination="next" :no-gap="true"/>
- </div>
-
- <div class="main _gap">
- <MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="fas fa-chevron-up"></i></MkButton>
- <div class="note _gap">
- <MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_isolated"/>
- <XNoteDetailed v-model:note="note" :key="note.id" class="_isolated note"/>
- </div>
- <div class="_content clips _gap" v-if="clips && clips.length > 0">
- <div class="title">{{ $ts.clip }}</div>
- <MkA v-for="item in clips" :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>
- <div class="user">
- <MkAvatar :user="item.user" class="avatar" :show-indicator="true"/> <MkUserName :user="item.user" :nowrap="false"/>
- </div>
- </MkA>
- </div>
- <MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="fas fa-chevron-down"></i></MkButton>
- </div>
-
- <div class="_gap" v-if="showPrev">
- <XNotes class="_content" :pagination="prev" :no-gap="true"/>
- </div>
- </div>
- <MkError v-else-if="error" @retry="fetch()"/>
- <MkLoading v-else/>
- </transition>
- </div>
-</MkSpacer>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import XNote from '@client/components/note.vue';
-import XNoteDetailed from '@client/components/note-detailed.vue';
-import XNotes from '@client/components/notes.vue';
-import MkRemoteCaution from '@client/components/remote-caution.vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XNote,
- XNoteDetailed,
- XNotes,
- MkRemoteCaution,
- MkButton,
- },
- props: {
- noteId: {
- type: String,
- required: true
- }
- },
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.note ? {
- title: this.$ts.note,
- subtitle: new Date(this.note.createdAt).toLocaleString(),
- avatar: this.note.user,
- path: `/notes/${this.note.id}`,
- share: {
- title: this.$t('noteOf', { user: this.note.user.name }),
- text: this.note.text,
- },
- bg: 'var(--bg)',
- } : null),
- note: null,
- clips: null,
- hasPrev: false,
- hasNext: false,
- showPrev: false,
- showNext: false,
- error: null,
- prev: {
- endpoint: 'users/notes',
- limit: 10,
- params: init => ({
- userId: this.note.userId,
- untilId: this.note.id,
- })
- },
- next: {
- reversed: true,
- endpoint: 'users/notes',
- limit: 10,
- params: init => ({
- userId: this.note.userId,
- sinceId: this.note.id,
- })
- },
- };
- },
- watch: {
- noteId: 'fetch'
- },
- created() {
- this.fetch();
- },
- methods: {
- fetch() {
- this.note = null;
- os.api('notes/show', {
- noteId: this.noteId
- }).then(note => {
- this.note = note;
- Promise.all([
- os.api('notes/clips', {
- noteId: note.id,
- }),
- os.api('users/notes', {
- userId: note.userId,
- untilId: note.id,
- limit: 1,
- }),
- os.api('users/notes', {
- userId: note.userId,
- sinceId: note.id,
- limit: 1,
- }),
- ]).then(([clips, prev, next]) => {
- this.clips = clips;
- this.hasPrev = prev.length !== 0;
- this.hasNext = next.length !== 0;
- });
- }).catch(e => {
- this.error = e;
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
- transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
- opacity: 0;
-}
-
-.fcuexfpr {
- background: var(--bg);
-
- > .note {
- > .main {
- > .load {
- min-width: 0;
- margin: 0 auto;
- border-radius: 999px;
-
- &.next {
- margin-bottom: var(--margin);
- }
-
- &.prev {
- margin-top: var(--margin);
- }
- }
-
- > .note {
- > .note {
- border-radius: var(--radius);
- background: var(--panel);
- }
- }
-
- > .clips {
- > .title {
- font-weight: bold;
- padding: 12px;
- }
-
- > .item {
- display: block;
- padding: 16px;
-
- > .description {
- padding: 8px 0;
- }
-
- > .user {
- $height: 32px;
- padding-top: 16px;
- border-top: solid 0.5px var(--divider);
- line-height: $height;
-
- > .avatar {
- width: $height;
- height: $height;
- }
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue
deleted file mode 100644
index 8d6adec48d..0000000000
--- a/src/client/pages/notifications.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-<template>
-<MkSpacer :content-max="800">
- <div class="clupoqwt">
- <XNotifications class="notifications" @before="before" @after="after" :include-types="includeTypes" :unread-only="tab === 'unread'"/>
- </div>
-</MkSpacer>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import XNotifications from '@client/components/notifications.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { notificationTypes } from '@/types';
-
-export default defineComponent({
- components: {
- XNotifications
- },
-
- 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: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- },
-
- 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);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.clupoqwt {
-}
-</style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.button.vue b/src/client/pages/page-editor/els/page-editor.el.button.vue
deleted file mode 100644
index 85e9d7e711..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.button.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.button }}</template>
-
- <section class="xfhsjczc">
- <MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._button.text }}</template></MkInput>
- <MkSwitch v-model="value.primary"><span>{{ $ts._pages.blocks._button.colored }}</span></MkSwitch>
- <MkSelect v-model="value.action">
- <template #label>{{ $ts._pages.blocks._button.action }}</template>
- <option value="dialog">{{ $ts._pages.blocks._button._action.dialog }}</option>
- <option value="resetRandom">{{ $ts._pages.blocks._button._action.resetRandom }}</option>
- <option value="pushEvent">{{ $ts._pages.blocks._button._action.pushEvent }}</option>
- <option value="callAiScript">{{ $ts._pages.blocks._button._action.callAiScript }}</option>
- </MkSelect>
- <template v-if="value.action === 'dialog'">
- <MkInput v-model="value.content"><template #label>{{ $ts._pages.blocks._button._action._dialog.content }}</template></MkInput>
- </template>
- <template v-else-if="value.action === 'pushEvent'">
- <MkInput v-model="value.event"><template #label>{{ $ts._pages.blocks._button._action._pushEvent.event }}</template></MkInput>
- <MkInput v-model="value.message"><template #label>{{ $ts._pages.blocks._button._action._pushEvent.message }}</template></MkInput>
- <MkSelect v-model="value.var">
- <template #label>{{ $ts._pages.blocks._button._action._pushEvent.variable }}</template>
- <option :value="null">{{ $t('_pages.blocks._button._action._pushEvent.no-variable') }}</option>
- <option v-for="v in hpml.getVarsByType()" :value="v.name">{{ v.name }}</option>
- <optgroup :label="$ts._pages.script.pageVariables">
- <option v-for="v in hpml.getPageVarsByType()" :value="v">{{ v }}</option>
- </optgroup>
- <optgroup :label="$ts._pages.script.enviromentVariables">
- <option v-for="v in hpml.getEnvVarsByType()" :value="v">{{ v }}</option>
- </optgroup>
- </MkSelect>
- </template>
- <template v-else-if="value.action === 'callAiScript'">
- <MkInput v-model="value.fn"><template #label>{{ $ts._pages.blocks._button._action._callAiScript.functionName }}</template></MkInput>
- </template>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkSelect, MkInput, MkSwitch
- },
-
- props: {
- value: {
- required: true
- },
- hpml: {
- required: true,
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.text == null) this.value.text = '';
- if (this.value.action == null) this.value.action = 'dialog';
- if (this.value.content == null) this.value.content = null;
- if (this.value.event == null) this.value.event = null;
- if (this.value.message == null) this.value.message = null;
- if (this.value.primary == null) this.value.primary = false;
- if (this.value.var == null) this.value.var = null;
- if (this.value.fn == null) this.value.fn = null;
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.xfhsjczc {
- padding: 0 16px 0 16px;
-}
-</style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.canvas.vue b/src/client/pages/page-editor/els/page-editor.el.canvas.vue
deleted file mode 100644
index c40d69a7c1..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.canvas.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-paint-brush"></i> {{ $ts._pages.blocks.canvas }}</template>
-
- <section style="padding: 0 16px 0 16px;">
- <MkInput v-model="value.name">
- <template #prefix><i class="fas fa-magic"></i></template>
- <template #label>{{ $ts._pages.blocks._canvas.id }}</template>
- </MkInput>
- <MkInput v-model="value.width" type="number">
- <template #label>{{ $ts._pages.blocks._canvas.width }}</template>
- <template #suffix>px</template>
- </MkInput>
- <MkInput v-model="value.height" type="number">
- <template #label>{{ $ts._pages.blocks._canvas.height }}</template>
- <template #suffix>px</template>
- </MkInput>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkInput
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.name == null) this.value.name = '';
- if (this.value.width == null) this.value.width = 300;
- if (this.value.height == null) this.value.height = 200;
- },
-});
-</script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.counter.vue b/src/client/pages/page-editor/els/page-editor.el.counter.vue
deleted file mode 100644
index de7994e3ba..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.counter.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.counter }}</template>
-
- <section style="padding: 0 16px 0 16px;">
- <MkInput v-model="value.name">
- <template #prefix><i class="fas fa-magic"></i></template>
- <template #label>{{ $ts._pages.blocks._counter.name }}</template>
- </MkInput>
- <MkInput v-model="value.text">
- <template #label>{{ $ts._pages.blocks._counter.text }}</template>
- </MkInput>
- <MkInput v-model="value.inc" type="number">
- <template #label>{{ $ts._pages.blocks._counter.inc }}</template>
- </MkInput>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkInput
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.name == null) this.value.name = '';
- },
-});
-</script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.if.vue b/src/client/pages/page-editor/els/page-editor.el.if.vue
deleted file mode 100644
index 52f4dac22e..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.if.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-question"></i> {{ $ts._pages.blocks.if }}</template>
- <template #func>
- <button @click="add()" class="_button">
- <i class="fas fa-plus"></i>
- </button>
- </template>
-
- <section class="romcojzs">
- <MkSelect v-model="value.var">
- <template #label>{{ $ts._pages.blocks._if.variable }}</template>
- <option v-for="v in hpml.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
- <optgroup :label="$ts._pages.script.pageVariables">
- <option v-for="v in hpml.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
- </optgroup>
- <optgroup :label="$ts._pages.script.enviromentVariables">
- <option v-for="v in hpml.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
- </optgroup>
- </MkSelect>
-
- <XBlocks class="children" v-model="value.children" :hpml="hpml"/>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent } from 'vue';
-import { v4 as uuid } from 'uuid';
-import XContainer from '../page-editor.container.vue';
-import MkSelect from '@client/components/form/select.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkSelect,
- XBlocks: defineAsyncComponent(() => import('../page-editor.blocks.vue')),
- },
-
- inject: ['getPageBlockList'],
-
- props: {
- value: {
- required: true
- },
- hpml: {
- required: true,
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.children == null) this.value.children = [];
- if (this.value.var === undefined) this.value.var = null;
- },
-
- methods: {
- async add() {
- const { canceled, result: type } = await os.dialog({
- type: null,
- title: this.$ts._pages.chooseBlock,
- select: {
- groupedItems: this.getPageBlockList()
- },
- showCancelButton: true
- });
- if (canceled) return;
-
- const id = uuid();
- this.value.children.push({ id, type });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.romcojzs {
- padding: 0 16px 16px 16px;
-}
-</style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.image.vue b/src/client/pages/page-editor/els/page-editor.el.image.vue
deleted file mode 100644
index d96879f50d..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.image.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-image"></i> {{ $ts._pages.blocks.image }}</template>
- <template #func>
- <button @click="choose()">
- <i class="fas fa-folder-open"></i>
- </button>
- </template>
-
- <section class="oyyftmcf">
- <MkDriveFileThumbnail class="preview" v-if="file" :file="file" fit="contain" @click="choose()"/>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkDriveFileThumbnail from '@client/components/drive-file-thumbnail.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkDriveFileThumbnail
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- file: null,
- };
- },
-
- created() {
- if (this.value.fileId === undefined) this.value.fileId = null;
- },
-
- mounted() {
- if (this.value.fileId == null) {
- this.choose();
- } else {
- os.api('drive/files/show', {
- fileId: this.value.fileId
- }).then(file => {
- this.file = file;
- });
- }
- },
-
- methods: {
- async choose() {
- os.selectDriveFile(false).then(file => {
- this.file = file;
- this.value.fileId = file.id;
- });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.oyyftmcf {
- > .preview {
- height: 150px;
- }
-}
-</style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.note.vue b/src/client/pages/page-editor/els/page-editor.el.note.vue
deleted file mode 100644
index 9feec395b7..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.note.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-sticky-note"></i> {{ $ts._pages.blocks.note }}</template>
-
- <section style="padding: 0 16px 0 16px;">
- <MkInput v-model="id">
- <template #label>{{ $ts._pages.blocks._note.id }}</template>
- <template #caption>{{ $ts._pages.blocks._note.idDescription }}</template>
- </MkInput>
- <MkSwitch v-model="value.detailed"><span>{{ $ts._pages.blocks._note.detailed }}</span></MkSwitch>
-
- <XNote v-if="note && !value.detailed" v-model:note="note" :key="note.id + ':normal'" style="margin-bottom: 16px;"/>
- <XNoteDetailed v-if="note && value.detailed" v-model:note="note" :key="note.id + ':detail'" style="margin-bottom: 16px;"/>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import XNote from '@client/components/note.vue';
-import XNoteDetailed from '@client/components/note-detailed.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkInput, MkSwitch, XNote, XNoteDetailed,
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- id: this.value.note,
- note: null,
- };
- },
-
- watch: {
- id: {
- async handler() {
- if (this.id && (this.id.startsWith('http://') || this.id.startsWith('https://'))) {
- this.value.note = this.id.endsWith('/') ? this.id.substr(0, this.id.length - 1).split('/').pop() : this.id.split('/').pop();
- } else {
- this.value.note = this.id;
- }
-
- this.note = await os.api('notes/show', { noteId: this.value.note });
- },
- immediate: true
- },
- },
-
- created() {
- if (this.value.note == null) this.value.note = null;
- if (this.value.detailed == null) this.value.detailed = false;
- },
-});
-</script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.number-input.vue b/src/client/pages/page-editor/els/page-editor.el.number-input.vue
deleted file mode 100644
index 57b1397824..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.number-input.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.numberInput }}</template>
-
- <section style="padding: 0 16px 0 16px;">
- <MkInput v-model="value.name">
- <template #prefix><i class="fas fa-magic"></i></template>
- <template #label>{{ $ts._pages.blocks._numberInput.name }}</template>
- </MkInput>
- <MkInput v-model="value.text">
- <template #label>{{ $ts._pages.blocks._numberInput.text }}</template>
- </MkInput>
- <MkInput v-model="value.default" type="number">
- <template #label>{{ $ts._pages.blocks._numberInput.default }}</template>
- </MkInput>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkInput
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.name == null) this.value.name = '';
- },
-});
-</script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.post.vue b/src/client/pages/page-editor/els/page-editor.el.post.vue
deleted file mode 100644
index e21ccfd345..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.post.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-paper-plane"></i> {{ $ts._pages.blocks.post }}</template>
-
- <section style="padding: 16px;">
- <MkTextarea v-model="value.text"><template #label>{{ $ts._pages.blocks._post.text }}</template></MkTextarea>
- <MkSwitch v-model="value.attachCanvasImage"><span>{{ $ts._pages.blocks._post.attachCanvasImage }}</span></MkSwitch>
- <MkInput v-if="value.attachCanvasImage" v-model="value.canvasId"><template #label>{{ $ts._pages.blocks._post.canvasId }}</template></MkInput>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkTextarea, MkInput, MkSwitch
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.text == null) this.value.text = '';
- if (this.value.attachCanvasImage == null) this.value.attachCanvasImage = false;
- if (this.value.canvasId == null) this.value.canvasId = '';
- },
-});
-</script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.radio-button.vue b/src/client/pages/page-editor/els/page-editor.el.radio-button.vue
deleted file mode 100644
index 62fb231f79..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.radio-button.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.radioButton }}</template>
-
- <section style="padding: 0 16px 16px 16px;">
- <MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._radioButton.name }}</template></MkInput>
- <MkInput v-model="value.title"><template #label>{{ $ts._pages.blocks._radioButton.title }}</template></MkInput>
- <MkTextarea v-model="values"><template #label>{{ $ts._pages.blocks._radioButton.values }}</template></MkTextarea>
- <MkInput v-model="value.default"><template #label>{{ $ts._pages.blocks._radioButton.default }}</template></MkInput>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkTextarea, MkInput
- },
- props: {
- value: {
- required: true
- },
- },
- data() {
- return {
- values: '',
- };
- },
- watch: {
- values: {
- handler() {
- this.value.values = this.values.split('\n');
- },
- deep: true
- }
- },
- created() {
- if (this.value.name == null) this.value.name = '';
- if (this.value.title == null) this.value.title = '';
- if (this.value.values == null) this.value.values = [];
- this.values = this.value.values.join('\n');
- },
-});
-</script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.section.vue b/src/client/pages/page-editor/els/page-editor.el.section.vue
deleted file mode 100644
index 75bdf120c0..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.section.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-sticky-note"></i> {{ value.title }}</template>
- <template #func>
- <button @click="rename()" class="_button">
- <i class="fas fa-pencil-alt"></i>
- </button>
- <button @click="add()" class="_button">
- <i class="fas fa-plus"></i>
- </button>
- </template>
-
- <section class="ilrvjyvi">
- <XBlocks class="children" v-model="value.children" :hpml="hpml"/>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent } from 'vue';
-import { v4 as uuid } from 'uuid';
-import XContainer from '../page-editor.container.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer,
- XBlocks: defineAsyncComponent(() => import('../page-editor.blocks.vue')),
- },
-
- inject: ['getPageBlockList'],
-
- props: {
- value: {
- required: true
- },
- hpml: {
- required: true,
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.title == null) this.value.title = null;
- if (this.value.children == null) this.value.children = [];
- },
-
- mounted() {
- if (this.value.title == null) {
- this.rename();
- }
- },
-
- methods: {
- async rename() {
- const { canceled, result: title } = await os.dialog({
- title: 'Enter title',
- input: {
- type: 'text',
- default: this.value.title
- },
- showCancelButton: true
- });
- if (canceled) return;
- this.value.title = title;
- },
-
- async add() {
- const { canceled, result: type } = await os.dialog({
- type: null,
- title: this.$ts._pages.chooseBlock,
- select: {
- groupedItems: this.getPageBlockList()
- },
- showCancelButton: true
- });
- if (canceled) return;
-
- const id = uuid();
- this.value.children.push({ id, type });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.ilrvjyvi {
- > .children {
- padding: 16px;
- }
-}
-</style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.switch.vue b/src/client/pages/page-editor/els/page-editor.el.switch.vue
deleted file mode 100644
index cf15f58c82..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.switch.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.switch }}</template>
-
- <section class="kjuadyyj">
- <MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._switch.name }}</template></MkInput>
- <MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._switch.text }}</template></MkInput>
- <MkSwitch v-model="value.default"><span>{{ $ts._pages.blocks._switch.default }}</span></MkSwitch>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkSwitch, MkInput
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.name == null) this.value.name = '';
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.kjuadyyj {
- padding: 0 16px 16px 16px;
-}
-</style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.text-input.vue b/src/client/pages/page-editor/els/page-editor.el.text-input.vue
deleted file mode 100644
index 210199befd..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.text-input.vue
+++ /dev/null
@@ -1,39 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.textInput }}</template>
-
- <section style="padding: 0 16px 0 16px;">
- <MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._textInput.name }}</template></MkInput>
- <MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._textInput.text }}</template></MkInput>
- <MkInput v-model="value.default" type="text"><template #label>{{ $ts._pages.blocks._textInput.default }}</template></MkInput>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkInput
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.name == null) this.value.name = '';
- },
-});
-</script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.text.vue b/src/client/pages/page-editor/els/page-editor.el.text.vue
deleted file mode 100644
index 668dd5f52d..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.text.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-align-left"></i> {{ $ts._pages.blocks.text }}</template>
-
- <section class="vckmsadr">
- <textarea v-model="value.text"></textarea>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.text == null) this.value.text = '';
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.vckmsadr {
- > textarea {
- display: block;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- width: 100%;
- min-width: 100%;
- min-height: 150px;
- border: none;
- box-shadow: none;
- padding: 16px;
- background: transparent;
- color: var(--fg);
- font-size: 14px;
- box-sizing: border-box;
- }
-}
-</style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue b/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue
deleted file mode 100644
index 14f36db2a1..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue
+++ /dev/null
@@ -1,40 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.textareaInput }}</template>
-
- <section style="padding: 0 16px 16px 16px;">
- <MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._textareaInput.name }}</template></MkInput>
- <MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._textareaInput.text }}</template></MkInput>
- <MkTextarea v-model="value.default"><template #label>{{ $ts._pages.blocks._textareaInput.default }}</template></MkTextarea>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer, MkTextarea, MkInput
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.name == null) this.value.name = '';
- },
-});
-</script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.textarea.vue b/src/client/pages/page-editor/els/page-editor.el.textarea.vue
deleted file mode 100644
index a29d5bd3f2..0000000000
--- a/src/client/pages/page-editor/els/page-editor.el.textarea.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<template>
-<XContainer @remove="() => $emit('remove')" :draggable="true">
- <template #header><i class="fas fa-align-left"></i> {{ $ts._pages.blocks.textarea }}</template>
-
- <section class="ihymsbbe">
- <textarea v-model="value.text"></textarea>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XContainer from '../page-editor.container.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XContainer
- },
-
- props: {
- value: {
- required: true
- },
- },
-
- data() {
- return {
- };
- },
-
- created() {
- if (this.value.text == null) this.value.text = '';
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.ihymsbbe {
- > textarea {
- display: block;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- width: 100%;
- min-width: 100%;
- min-height: 150px;
- border: none;
- box-shadow: none;
- padding: 16px;
- background: transparent;
- color: var(--fg);
- font-size: 14px;
- box-sizing: border-box;
- }
-}
-</style>
diff --git a/src/client/pages/page-editor/page-editor.blocks.vue b/src/client/pages/page-editor/page-editor.blocks.vue
deleted file mode 100644
index c27162a26e..0000000000
--- a/src/client/pages/page-editor/page-editor.blocks.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<template>
-<XDraggable tag="div" v-model="blocks" item-key="id" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5">
- <template #item="{element}">
- <component :is="'x-' + element.type" :value="element" @update:value="updateItem" @remove="() => removeItem(element)" :hpml="hpml"/>
- </template>
-</XDraggable>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent } from 'vue';
-import XSection from './els/page-editor.el.section.vue';
-import XText from './els/page-editor.el.text.vue';
-import XTextarea from './els/page-editor.el.textarea.vue';
-import XImage from './els/page-editor.el.image.vue';
-import XButton from './els/page-editor.el.button.vue';
-import XTextInput from './els/page-editor.el.text-input.vue';
-import XTextareaInput from './els/page-editor.el.textarea-input.vue';
-import XNumberInput from './els/page-editor.el.number-input.vue';
-import XSwitch from './els/page-editor.el.switch.vue';
-import XIf from './els/page-editor.el.if.vue';
-import XPost from './els/page-editor.el.post.vue';
-import XCounter from './els/page-editor.el.counter.vue';
-import XRadioButton from './els/page-editor.el.radio-button.vue';
-import XCanvas from './els/page-editor.el.canvas.vue';
-import XNote from './els/page-editor.el.note.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
- XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas, XNote
- },
-
- props: {
- modelValue: {
- type: Array,
- required: true
- },
- hpml: {
- required: true,
- },
- },
-
- emits: ['update:modelValue'],
-
- computed: {
- blocks: {
- get() {
- return this.modelValue;
- },
- set(value) {
- this.$emit('update:modelValue', value);
- }
- }
- },
-
- methods: {
- updateItem(v) {
- const i = this.blocks.findIndex(x => x.id === v.id);
- const newValue = [
- ...this.blocks.slice(0, i),
- v,
- ...this.blocks.slice(i + 1)
- ];
- this.$emit('update:modelValue', newValue);
- },
-
- removeItem(el) {
- const i = this.blocks.findIndex(x => x.id === el.id);
- const newValue = [
- ...this.blocks.slice(0, i),
- ...this.blocks.slice(i + 1)
- ];
- this.$emit('update:modelValue', newValue);
- },
- }
-});
-</script>
diff --git a/src/client/pages/page-editor/page-editor.container.vue b/src/client/pages/page-editor/page-editor.container.vue
deleted file mode 100644
index afd261fac7..0000000000
--- a/src/client/pages/page-editor/page-editor.container.vue
+++ /dev/null
@@ -1,159 +0,0 @@
-<template>
-<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }">
- <header>
- <div class="title"><slot name="header"></slot></div>
- <div class="buttons">
- <slot name="func"></slot>
- <button v-if="removable" @click="remove()" class="_button">
- <i class="fas fa-trash-alt"></i>
- </button>
- <button v-if="draggable" class="drag-handle _button">
- <i class="fas fa-bars"></i>
- </button>
- <button @click="toggleContent(!showBody)" class="_button">
- <template v-if="showBody"><i class="fas fa-angle-up"></i></template>
- <template v-else><i class="fas fa-angle-down"></i></template>
- </button>
- </div>
- </header>
- <p v-show="showBody" class="error" v-if="error != null">{{ $t('_pages.script.typeError', { slot: error.arg + 1, expect: $t(`script.types.${error.expect}`), actual: $t(`script.types.${error.actual}`) }) }}</p>
- <p v-show="showBody" class="warn" v-if="warn != null">{{ $t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
- <div v-show="showBody" class="body">
- <slot></slot>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
- props: {
- expanded: {
- type: Boolean,
- default: true
- },
- removable: {
- type: Boolean,
- default: true
- },
- draggable: {
- type: Boolean,
- default: false
- },
- error: {
- required: false,
- default: null
- },
- warn: {
- required: false,
- default: null
- }
- },
- emits: ['toggle', 'remove'],
- data() {
- return {
- showBody: this.expanded,
- };
- },
- methods: {
- toggleContent(show: boolean) {
- this.showBody = show;
- this.$emit('toggle', show);
- },
- remove() {
- this.$emit('remove');
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.cpjygsrt {
- position: relative;
- overflow: hidden;
- background: var(--panel);
- border: solid 2px var(--X12);
- border-radius: 6px;
-
- &:hover {
- border: solid 2px var(--X13);
- }
-
- &.warn {
- border: solid 2px #dec44c;
- }
-
- &.error {
- border: solid 2px #f00;
- }
-
- & + .cpjygsrt {
- margin-top: 16px;
- }
-
- > header {
- > .title {
- z-index: 1;
- margin: 0;
- padding: 0 16px;
- line-height: 42px;
- font-size: 0.9em;
- font-weight: bold;
- box-shadow: 0 1px rgba(#000, 0.07);
-
- > i {
- margin-right: 6px;
- }
-
- &:empty {
- display: none;
- }
- }
-
- > .buttons {
- position: absolute;
- z-index: 2;
- top: 0;
- right: 0;
-
- > button {
- padding: 0;
- width: 42px;
- font-size: 0.9em;
- line-height: 42px;
- }
-
- .drag-handle {
- cursor: move;
- }
- }
- }
-
- > .warn {
- color: #b19e49;
- margin: 0;
- padding: 16px 16px 0 16px;
- font-size: 14px;
- }
-
- > .error {
- color: #f00;
- margin: 0;
- padding: 16px 16px 0 16px;
- font-size: 14px;
- }
-
- > .body {
- ::v-deep(.juejbjww), ::v-deep(.eiipwacr) {
- &:not(.inline):first-child {
- margin-top: 28px;
- }
-
- &:not(.inline):last-child {
- margin-bottom: 20px;
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/page-editor/page-editor.script-block.vue b/src/client/pages/page-editor/page-editor.script-block.vue
deleted file mode 100644
index 3313fc1ba9..0000000000
--- a/src/client/pages/page-editor/page-editor.script-block.vue
+++ /dev/null
@@ -1,281 +0,0 @@
-<template>
-<XContainer :removable="removable" @remove="() => $emit('remove')" :error="error" :warn="warn" :draggable="draggable">
- <template #header><i v-if="icon" :class="icon"></i> <template v-if="title">{{ title }} <span class="turmquns" v-if="typeText">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template>
- <template #func>
- <button @click="changeType()" class="_button">
- <i class="fas fa-pencil-alt"></i>
- </button>
- </template>
-
- <section v-if="modelValue.type === null" class="pbglfege" @click="changeType()">
- {{ $ts._pages.script.emptySlot }}
- </section>
- <section v-else-if="modelValue.type === 'text'" class="tbwccoaw">
- <input v-model="modelValue.value"/>
- </section>
- <section v-else-if="modelValue.type === 'multiLineText'" class="tbwccoaw">
- <textarea v-model="modelValue.value"></textarea>
- </section>
- <section v-else-if="modelValue.type === 'textList'" class="tbwccoaw">
- <textarea v-model="modelValue.value" :placeholder="$ts._pages.script.blocks._textList.info"></textarea>
- </section>
- <section v-else-if="modelValue.type === 'number'" class="tbwccoaw">
- <input v-model="modelValue.value" type="number"/>
- </section>
- <section v-else-if="modelValue.type === 'ref'" class="hpdwcrvs">
- <select v-model="modelValue.value">
- <option v-for="v in hpml.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
- <optgroup :label="$ts._pages.script.argVariables">
- <option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option>
- </optgroup>
- <optgroup :label="$ts._pages.script.pageVariables">
- <option v-for="v in hpml.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
- </optgroup>
- <optgroup :label="$ts._pages.script.enviromentVariables">
- <option v-for="v in hpml.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
- </optgroup>
- </select>
- </section>
- <section v-else-if="modelValue.type === 'aiScriptVar'" class="tbwccoaw">
- <input v-model="modelValue.value"/>
- </section>
- <section v-else-if="modelValue.type === 'fn'" class="" style="padding:0 16px 16px 16px;">
- <MkTextarea v-model="slots">
- <template #label>{{ $ts._pages.script.blocks._fn.slots }}</template>
- <template #caption>{{ $t('_pages.script.blocks._fn.slots-info') }}</template>
- </MkTextarea>
- <XV v-if="modelValue.value.expression" v-model="modelValue.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :hpml="hpml" :fn-slots="value.value.slots" :name="name"/>
- </section>
- <section v-else-if="modelValue.type.startsWith('fn:')" class="" style="padding:16px;">
- <XV v-for="(x, i) in modelValue.args" v-model="value.args[i]" :title="hpml.getVarByName(modelValue.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :hpml="hpml" :name="name" :key="i"/>
- </section>
- <section v-else class="" style="padding:16px;">
- <XV v-for="(x, i) in modelValue.args" v-model="modelValue.args[i]" :title="$t(`_pages.script.blocks._${modelValue.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :hpml="hpml" :name="name" :fn-slots="fnSlots" :key="i"/>
- </section>
-</XContainer>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import { v4 as uuid } from 'uuid';
-import XContainer from './page-editor.container.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import { blockDefs } from '@client/scripts/hpml/index';
-import * as os from '@client/os';
-import { isLiteralValue } from '@client/scripts/hpml/expr';
-import { funcDefs } from '@client/scripts/hpml/lib';
-
-export default defineComponent({
- components: {
- XContainer, MkTextarea,
- XV: defineAsyncComponent(() => import('./page-editor.script-block.vue')),
- },
-
- inject: ['getScriptBlockList'],
-
- props: {
- getExpectedType: {
- required: false,
- default: null
- },
- modelValue: {
- required: true
- },
- title: {
- required: false
- },
- removable: {
- required: false,
- default: false
- },
- hpml: {
- required: true,
- },
- name: {
- required: true,
- },
- fnSlots: {
- required: false,
- },
- draggable: {
- required: false,
- default: false
- }
- },
-
- data() {
- return {
- error: null,
- warn: null,
- slots: '',
- };
- },
-
- computed: {
- icon(): any {
- if (this.modelValue.type === null) return null;
- if (this.modelValue.type.startsWith('fn:')) return 'fas fa-plug';
- return blockDefs.find(x => x.type === this.modelValue.type).icon;
- },
- typeText(): any {
- if (this.modelValue.type === null) return null;
- if (this.modelValue.type.startsWith('fn:')) return this.modelValue.type.split(':')[1];
- return this.$t(`_pages.script.blocks.${this.modelValue.type}`);
- },
- },
-
- watch: {
- slots: {
- handler() {
- this.modelValue.value.slots = this.slots.split('\n').map(x => ({
- name: x,
- type: null
- }));
- },
- deep: true
- }
- },
-
- created() {
- if (this.modelValue.value == null) this.modelValue.value = null;
-
- if (this.modelValue.value && this.modelValue.value.slots) this.slots = this.modelValue.value.slots.map(x => x.name).join('\n');
-
- this.$watch(() => this.modelValue.type, (t) => {
- this.warn = null;
-
- if (this.modelValue.type === 'fn') {
- const id = uuid();
- this.modelValue.value = {
- slots: [],
- expression: { id, type: null }
- };
- return;
- }
-
- if (this.modelValue.type && this.modelValue.type.startsWith('fn:')) {
- const fnName = this.modelValue.type.split(':')[1];
- const fn = this.hpml.getVarByName(fnName);
-
- const empties = [];
- for (let i = 0; i < fn.value.slots.length; i++) {
- const id = uuid();
- empties.push({ id, type: null });
- }
- this.modelValue.args = empties;
- return;
- }
-
- if (isLiteralValue(this.modelValue)) return;
-
- const empties = [];
- for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) {
- const id = uuid();
- empties.push({ id, type: null });
- }
- this.modelValue.args = empties;
-
- for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) {
- const inType = funcDefs[this.modelValue.type].in[i];
- if (typeof inType !== 'number') {
- if (inType === 'number') this.modelValue.args[i].type = 'number';
- if (inType === 'string') this.modelValue.args[i].type = 'text';
- }
- }
- });
-
- this.$watch(() => this.modelValue.args, (args) => {
- if (args == null) {
- this.warn = null;
- return;
- }
- const emptySlotIndex = args.findIndex(x => x.type === null);
- if (emptySlotIndex !== -1 && emptySlotIndex < args.length) {
- this.warn = {
- slot: emptySlotIndex
- };
- } else {
- this.warn = null;
- }
- }, {
- deep: true
- });
-
- this.$watch(() => this.hpml.variables, () => {
- if (this.type != null && this.modelValue) {
- this.error = this.hpml.typeCheck(this.modelValue);
- }
- }, {
- deep: true
- });
- },
-
- methods: {
- async changeType() {
- const { canceled, result: type } = await os.dialog({
- type: null,
- title: this.$ts._pages.selectType,
- select: {
- groupedItems: this.getScriptBlockList(this.getExpectedType ? this.getExpectedType() : null)
- },
- showCancelButton: true
- });
- if (canceled) return;
- this.modelValue.type = type;
- },
-
- _getExpectedType(slot: number) {
- return this.hpml.getExpectedType(this.modelValue, slot);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.turmquns {
- opacity: 0.7;
-}
-
-.pbglfege {
- opacity: 0.5;
- padding: 16px;
- text-align: center;
- cursor: pointer;
- color: var(--fg);
-}
-
-.tbwccoaw {
- > input,
- > textarea {
- display: block;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- width: 100%;
- max-width: 100%;
- min-width: 100%;
- border: none;
- box-shadow: none;
- padding: 16px;
- font-size: 16px;
- background: transparent;
- color: var(--fg);
- box-sizing: border-box;
- }
-
- > textarea {
- min-height: 100px;
- }
-}
-
-.hpdwcrvs {
- padding: 16px;
-
- > select {
- display: block;
- padding: 4px;
- font-size: 16px;
- width: 100%;
- }
-}
-</style>
diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue
deleted file mode 100644
index aefcc14564..0000000000
--- a/src/client/pages/page-editor/page-editor.vue
+++ /dev/null
@@ -1,561 +0,0 @@
-<template>
-<div>
- <div class="jqqmcavi" style="margin: 16px;">
- <MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="fas fa-external-link-square-alt"></i> {{ $ts._pages.viewPage }}</MkButton>
- <MkButton inline @click="save" primary class="button" v-if="!readonly"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
- <MkButton inline @click="duplicate" class="button" v-if="pageId"><i class="fas fa-copy"></i> {{ $ts.duplicate }}</MkButton>
- <MkButton inline @click="del" class="button" v-if="pageId && !readonly" danger><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton>
- </div>
-
- <div v-if="tab === 'settings'">
- <div style="padding: 16px;" class="_formRoot">
- <MkInput v-model="title" class="_formBlock">
- <template #label>{{ $ts._pages.title }}</template>
- </MkInput>
-
- <MkInput v-model="summary" class="_formBlock">
- <template #label>{{ $ts._pages.summary }}</template>
- </MkInput>
-
- <MkInput v-model="name" class="_formBlock">
- <template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
- <template #label>{{ $ts._pages.url }}</template>
- </MkInput>
-
- <MkSwitch v-model="alignCenter" class="_formBlock">{{ $ts._pages.alignCenter }}</MkSwitch>
-
- <MkSelect v-model="font" class="_formBlock">
- <template #label>{{ $ts._pages.font }}</template>
- <option value="serif">{{ $ts._pages.fontSerif }}</option>
- <option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option>
- </MkSelect>
-
- <MkSwitch v-model="hideTitleWhenPinned" class="_formBlock">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch>
-
- <div class="eyeCatch">
- <MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><i class="fas fa-plus"></i> {{ $ts._pages.eyeCatchingImageSet }}</MkButton>
- <div v-else-if="eyeCatchingImage">
- <img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/>
- <MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><i class="fas fa-trash-alt"></i> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton>
- </div>
- </div>
- </div>
- </div>
-
- <div v-else-if="tab === 'contents'">
- <div style="padding: 16px;">
- <XBlocks class="content" v-model="content" :hpml="hpml"/>
-
- <MkButton @click="add()" v-if="!readonly"><i class="fas fa-plus"></i></MkButton>
- </div>
- </div>
-
- <div v-else-if="tab === 'variables'">
- <div class="qmuvgica">
- <XDraggable tag="div" class="variables" v-show="variables.length > 0" v-model="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5">
- <template #item="{element}">
- <XVariable
- :modelValue="element"
- :removable="true"
- @remove="() => removeVariable(element)"
- :hpml="hpml"
- :name="element.name"
- :title="element.name"
- :draggable="true"
- />
- </template>
- </XDraggable>
-
- <MkButton @click="addVariable()" class="add" v-if="!readonly"><i class="fas fa-plus"></i></MkButton>
- </div>
- </div>
-
- <div v-else-if="tab === 'script'">
- <div>
- <MkTextarea class="_code" v-model="script"/>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import 'prismjs';
-import { highlight, languages } from 'prismjs/components/prism-core';
-import 'prismjs/components/prism-clike';
-import 'prismjs/components/prism-javascript';
-import 'prismjs/themes/prism-okaidia.css';
-import 'vue-prism-editor/dist/prismeditor.min.css';
-import { v4 as uuid } from 'uuid';
-import XVariable from './page-editor.script-block.vue';
-import XBlocks from './page-editor.blocks.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import MkInput from '@client/components/form/input.vue';
-import { blockDefs } from '@client/scripts/hpml/index';
-import { HpmlTypeChecker } from '@client/scripts/hpml/type-checker';
-import { url } from '@client/config';
-import { collectPageVars } from '@client/scripts/collect-page-vars';
-import * as os from '@client/os';
-import { selectFile } from '@client/scripts/select-file';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
- XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput,
- },
-
- props: {
- initPageId: {
- type: String,
- required: false
- },
- initPageName: {
- type: String,
- required: false
- },
- initUser: {
- type: String,
- required: false
- },
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => {
- let title = this.$ts._pages.newPage;
- if (this.initPageId) {
- title = this.$ts._pages.editPage;
- }
- else if (this.initPageName && this.initUser) {
- title = this.$ts._pages.readPage;
- }
- return {
- title: title,
- icon: 'fas fa-pencil-alt',
- bg: 'var(--bg)',
- tabs: [{
- active: this.tab === 'settings',
- title: this.$ts._pages.pageSetting,
- icon: 'fas fa-cog',
- onClick: () => { this.tab = 'settings'; },
- }, {
- active: this.tab === 'contents',
- title: this.$ts._pages.contents,
- icon: 'fas fa-sticky-note',
- onClick: () => { this.tab = 'contents'; },
- }, {
- active: this.tab === 'variables',
- title: this.$ts._pages.variables,
- icon: 'fas fa-magic',
- onClick: () => { this.tab = 'variables'; },
- }, {
- active: this.tab === 'script',
- title: this.$ts.script,
- icon: 'fas fa-code',
- onClick: () => { this.tab = 'script'; },
- }],
- };
- }),
- tab: 'settings',
- author: this.$i,
- readonly: false,
- page: null,
- pageId: null,
- currentName: null,
- title: '',
- summary: null,
- name: Date.now().toString(),
- eyeCatchingImage: null,
- eyeCatchingImageId: null,
- font: 'sans-serif',
- content: [],
- alignCenter: false,
- hideTitleWhenPinned: false,
- variables: [],
- hpml: null,
- script: '',
- url,
- };
- },
-
- watch: {
- async eyeCatchingImageId() {
- if (this.eyeCatchingImageId == null) {
- this.eyeCatchingImage = null;
- } else {
- this.eyeCatchingImage = await os.api('drive/files/show', {
- fileId: this.eyeCatchingImageId,
- });
- }
- },
- },
-
- async created() {
- this.hpml = new HpmlTypeChecker();
-
- this.$watch('variables', () => {
- this.hpml.variables = this.variables;
- }, { deep: true });
-
- this.$watch('content', () => {
- this.hpml.pageVars = collectPageVars(this.content);
- }, { deep: true });
-
- if (this.initPageId) {
- this.page = await os.api('pages/show', {
- pageId: this.initPageId,
- });
- } else if (this.initPageName && this.initUser) {
- this.page = await os.api('pages/show', {
- name: this.initPageName,
- username: this.initUser,
- });
- this.readonly = true;
- }
-
- if (this.page) {
- this.author = this.page.user;
- this.pageId = this.page.id;
- this.title = this.page.title;
- this.name = this.page.name;
- this.currentName = this.page.name;
- this.summary = this.page.summary;
- this.font = this.page.font;
- this.script = this.page.script;
- this.hideTitleWhenPinned = this.page.hideTitleWhenPinned;
- this.alignCenter = this.page.alignCenter;
- this.content = this.page.content;
- this.variables = this.page.variables;
- this.eyeCatchingImageId = this.page.eyeCatchingImageId;
- } else {
- const id = uuid();
- this.content = [{
- id,
- type: 'text',
- text: 'Hello World!'
- }];
- }
- },
-
- provide() {
- return {
- readonly: this.readonly,
- getScriptBlockList: this.getScriptBlockList,
- getPageBlockList: this.getPageBlockList
- }
- },
-
- methods: {
- getSaveOptions() {
- return {
- title: this.title.trim(),
- name: this.name.trim(),
- summary: this.summary,
- font: this.font,
- script: this.script,
- hideTitleWhenPinned: this.hideTitleWhenPinned,
- alignCenter: this.alignCenter,
- content: this.content,
- variables: this.variables,
- eyeCatchingImageId: this.eyeCatchingImageId,
- };
- },
-
- save() {
- const options = this.getSaveOptions();
-
- const onError = err => {
- if (err.id == '3d81ceae-475f-4600-b2a8-2bc116157532') {
- if (err.info.param == 'name') {
- os.dialog({
- type: 'error',
- title: this.$ts._pages.invalidNameTitle,
- text: this.$ts._pages.invalidNameText
- });
- }
- } else if (err.code == 'NAME_ALREADY_EXISTS') {
- os.dialog({
- type: 'error',
- text: this.$ts._pages.nameAlreadyExists
- });
- }
- };
-
- if (this.pageId) {
- options.pageId = this.pageId;
- os.api('pages/update', options)
- .then(page => {
- this.currentName = this.name.trim();
- os.dialog({
- type: 'success',
- text: this.$ts._pages.updated
- });
- }).catch(onError);
- } else {
- os.api('pages/create', options)
- .then(page => {
- this.pageId = page.id;
- this.currentName = this.name.trim();
- os.dialog({
- type: 'success',
- text: this.$ts._pages.created
- });
- this.$router.push(`/pages/edit/${this.pageId}`);
- }).catch(onError);
- }
- },
-
- del() {
- os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: this.title.trim() }),
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
- os.api('pages/delete', {
- pageId: this.pageId,
- }).then(() => {
- os.dialog({
- type: 'success',
- text: this.$ts._pages.deleted
- });
- this.$router.push(`/pages`);
- });
- });
- },
-
- duplicate() {
- this.title = this.title + ' - copy';
- this.name = this.name + '-copy';
- os.api('pages/create', this.getSaveOptions()).then(page => {
- this.pageId = page.id;
- this.currentName = this.name.trim();
- os.dialog({
- type: 'success',
- text: this.$ts._pages.created
- });
- this.$router.push(`/pages/edit/${this.pageId}`);
- });
- },
-
- async add() {
- const { canceled, result: type } = await os.dialog({
- type: null,
- title: this.$ts._pages.chooseBlock,
- select: {
- groupedItems: this.getPageBlockList()
- },
- showCancelButton: true
- });
- if (canceled) return;
-
- const id = uuid();
- this.content.push({ id, type });
- },
-
- async addVariable() {
- let { canceled, result: name } = await os.dialog({
- title: this.$ts._pages.enterVariableName,
- input: {
- type: 'text',
- },
- showCancelButton: true
- });
- if (canceled) return;
-
- name = name.trim();
-
- if (this.hpml.isUsedName(name)) {
- os.dialog({
- type: 'error',
- text: this.$ts._pages.variableNameIsAlreadyUsed
- });
- return;
- }
-
- const id = uuid();
- this.variables.push({ id, name, type: null });
- },
-
- removeVariable(v) {
- this.variables = this.variables.filter(x => x.name !== v.name);
- },
-
- getPageBlockList() {
- return [{
- label: this.$ts._pages.contentBlocks,
- items: [
- { value: 'section', text: this.$ts._pages.blocks.section },
- { value: 'text', text: this.$ts._pages.blocks.text },
- { value: 'image', text: this.$ts._pages.blocks.image },
- { value: 'textarea', text: this.$ts._pages.blocks.textarea },
- { value: 'note', text: this.$ts._pages.blocks.note },
- { value: 'canvas', text: this.$ts._pages.blocks.canvas },
- ]
- }, {
- label: this.$ts._pages.inputBlocks,
- items: [
- { value: 'button', text: this.$ts._pages.blocks.button },
- { value: 'radioButton', text: this.$ts._pages.blocks.radioButton },
- { value: 'textInput', text: this.$ts._pages.blocks.textInput },
- { value: 'textareaInput', text: this.$ts._pages.blocks.textareaInput },
- { value: 'numberInput', text: this.$ts._pages.blocks.numberInput },
- { value: 'switch', text: this.$ts._pages.blocks.switch },
- { value: 'counter', text: this.$ts._pages.blocks.counter }
- ]
- }, {
- label: this.$ts._pages.specialBlocks,
- items: [
- { value: 'if', text: this.$ts._pages.blocks.if },
- { value: 'post', text: this.$ts._pages.blocks.post }
- ]
- }];
- },
-
- getScriptBlockList(type: string = null) {
- const list = [];
-
- const blocks = blockDefs.filter(block => type === null || block.out === null || block.out === type || typeof block.out === 'number');
-
- for (const block of blocks) {
- const category = list.find(x => x.category === block.category);
- if (category) {
- category.items.push({
- value: block.type,
- text: this.$t(`_pages.script.blocks.${block.type}`)
- });
- } else {
- list.push({
- category: block.category,
- label: this.$t(`_pages.script.categories.${block.category}`),
- items: [{
- value: block.type,
- text: this.$t(`_pages.script.blocks.${block.type}`)
- }]
- });
- }
- }
-
- const userFns = this.variables.filter(x => x.type === 'fn');
- if (userFns.length > 0) {
- list.unshift({
- label: this.$t(`_pages.script.categories.fn`),
- items: userFns.map(v => ({
- value: 'fn:' + v.name,
- text: v.name
- }))
- });
- }
-
- return list;
- },
-
- setEyeCatchingImage(e) {
- selectFile(e.currentTarget || e.target, null, false).then(file => {
- this.eyeCatchingImageId = file.id;
- });
- },
-
- removeEyeCatchingImage() {
- this.eyeCatchingImageId = null;
- },
-
- highlighter(code) {
- return highlight(code, languages.js, 'javascript');
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.jqqmcavi {
- > .button {
- & + .button {
- margin-left: 8px;
- }
- }
-}
-
-.gwbmwxkm {
- position: relative;
-
- > header {
- > .title {
- z-index: 1;
- margin: 0;
- padding: 0 16px;
- line-height: 42px;
- font-size: 0.9em;
- font-weight: bold;
- box-shadow: 0 1px rgba(#000, 0.07);
-
- > i {
- margin-right: 6px;
- }
-
- &:empty {
- display: none;
- }
- }
-
- > .buttons {
- position: absolute;
- z-index: 2;
- top: 0;
- right: 0;
-
- > button {
- padding: 0;
- width: 42px;
- font-size: 0.9em;
- line-height: 42px;
- }
- }
- }
-
- > section {
- padding: 0 32px 32px 32px;
-
- @media (max-width: 500px) {
- padding: 0 16px 16px 16px;
- }
-
- > .view {
- display: inline-block;
- margin: 16px 0 0 0;
- font-size: 14px;
- }
-
- > .content {
- margin-bottom: 16px;
- }
-
- > .eyeCatch {
- margin-bottom: 16px;
-
- > div {
- > img {
- max-width: 100%;
- }
- }
- }
- }
-}
-
-.qmuvgica {
- padding: 16px;
-
- > .variables {
- margin-bottom: 16px;
- }
-
- > .add {
- margin-bottom: 16px;
- }
-}
-</style>
diff --git a/src/client/pages/page.vue b/src/client/pages/page.vue
deleted file mode 100644
index 3ea687a35d..0000000000
--- a/src/client/pages/page.vue
+++ /dev/null
@@ -1,311 +0,0 @@
-<template>
-<div>
- <transition name="fade" mode="out-in">
- <div v-if="page" class="xcukqgmh" :key="page.id" v-size="{ max: [450] }">
- <div class="_block main">
- <!--
- <div class="header">
- <h1>{{ page.title }}</h1>
- </div>
- -->
- <div class="banner">
- <img :src="page.eyeCatchingImage.url" v-if="page.eyeCatchingImageId"/>
- </div>
- <div class="content">
- <XPage :page="page"/>
- </div>
- <div class="actions">
- <div class="like">
- <MkButton class="button" @click="unlike()" v-if="page.isLiked" v-tooltip="$ts._pages.unlike" primary><i class="fas fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton>
- <MkButton class="button" @click="like()" v-else v-tooltip="$ts._pages.like"><i class="far fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton>
- </div>
- <div class="other">
- <button class="_button" @click="shareWithNote" v-tooltip="$ts.shareWithNote" v-click-anime><i class="fas fa-retweet fa-fw"></i></button>
- <button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button>
- </div>
- </div>
- <div class="user">
- <MkAvatar :user="page.user" class="avatar"/>
- <div class="name">
- <MkUserName :user="page.user" style="display: block;"/>
- <MkAcct :user="page.user"/>
- </div>
- <MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
- </div>
- <div class="links">
- <MkA :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ $ts._pages.viewSource }}</MkA>
- <template v-if="$i && $i.id === page.userId">
- <MkA :to="`/pages/edit/${page.id}`" class="link">{{ $ts._pages.editThisPage }}</MkA>
- <button v-if="$i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $ts.unpin }}</button>
- <button v-else @click="pin(true)" class="link _textButton">{{ $ts.pin }}</button>
- </template>
- </div>
- </div>
- <div class="footer">
- <div><i class="far fa-clock"></i> {{ $ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div>
- <div v-if="page.createdAt != page.updatedAt"><i class="far fa-clock"></i> {{ $ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div>
- </div>
- <MkAd :prefer="['horizontal', 'horizontal-big']"/>
- <MkContainer :max-height="300" :foldable="true" class="other">
- <template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
- <MkPagination :pagination="otherPostsPagination" #default="{items}">
- <MkPagePreview v-for="page in items" :page="page" :key="page.id" class="_gap"/>
- </MkPagination>
- </MkContainer>
- </div>
- <MkError v-else-if="error" @retry="fetch()"/>
- <MkLoading v-else/>
- </transition>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import XPage from '@client/components/page/page.vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { url } from '@client/config';
-import MkFollowButton from '@client/components/follow-button.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkPagePreview from '@client/components/page-preview.vue';
-
-export default defineComponent({
- components: {
- XPage,
- MkButton,
- MkFollowButton,
- MkContainer,
- MkPagination,
- MkPagePreview,
- },
-
- props: {
- pageName: {
- type: String,
- required: true
- },
- username: {
- type: String,
- required: true
- },
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.page ? {
- title: computed(() => this.page.title || this.page.name),
- avatar: this.page.user,
- path: `/@${this.page.user.username}/pages/${this.page.name}`,
- share: {
- title: this.page.title || this.page.name,
- text: this.page.summary,
- },
- } : null),
- page: null,
- error: null,
- otherPostsPagination: {
- endpoint: 'users/pages',
- limit: 6,
- params: () => ({
- userId: this.page.user.id
- })
- },
- };
- },
-
- computed: {
- path(): string {
- return this.username + '/' + this.pageName;
- }
- },
-
- watch: {
- path() {
- this.fetch();
- }
- },
-
- created() {
- this.fetch();
- },
-
- methods: {
- fetch() {
- this.page = null;
- os.api('pages/show', {
- name: this.pageName,
- username: this.username,
- }).then(page => {
- this.page = page;
- }).catch(e => {
- this.error = e;
- });
- },
-
- share() {
- navigator.share({
- title: this.page.title || this.page.name,
- text: this.page.summary,
- url: `${url}/@${this.page.user.username}/pages/${this.page.name}`
- });
- },
-
- shareWithNote() {
- os.post({
- initialText: `${this.page.title || this.page.name} ${url}/@${this.page.user.username}/pages/${this.page.name}`
- });
- },
-
- like() {
- os.apiWithDialog('pages/like', {
- pageId: this.page.id,
- }).then(() => {
- this.page.isLiked = true;
- this.page.likedCount++;
- });
- },
-
- async unlike() {
- const confirm = await os.dialog({
- type: 'warning',
- showCancelButton: true,
- text: this.$ts.unlikeConfirm,
- });
- if (confirm.canceled) return;
- os.apiWithDialog('pages/unlike', {
- pageId: this.page.id,
- }).then(() => {
- this.page.isLiked = false;
- this.page.likedCount--;
- });
- },
-
- pin(pin) {
- os.apiWithDialog('i/update', {
- pinnedPageId: pin ? this.page.id : null,
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
- transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
- opacity: 0;
-}
-
-.xcukqgmh {
- --padding: 32px;
-
- &.max-width_450px {
- --padding: 16px;
- }
-
- > .main {
- padding: var(--padding);
-
- > .header {
- padding: 16px;
-
- > h1 {
- margin: 0;
- }
- }
-
- > .banner {
- > img {
- // TODO: ่‰ฏใ„ๆ„Ÿใ˜ใฎใ‚ขใ‚นใƒšใ‚ฏใƒˆๆฏ”ใง่กจ็คบ
- display: block;
- width: 100%;
- height: 150px;
- object-fit: cover;
- }
- }
-
- > .content {
- margin-top: 16px;
- padding: 16px 0 0 0;
- }
-
- > .actions {
- display: flex;
- align-items: center;
- margin-top: 16px;
- padding: 16px 0 0 0;
- border-top: solid 0.5px var(--divider);
-
- > .like {
- > .button {
- --accent: rgb(241 97 132);
- --X8: rgb(241 92 128);
- --buttonBg: rgb(216 71 106 / 5%);
- --buttonHoverBg: rgb(216 71 106 / 10%);
- color: #ff002f;
-
- ::v-deep(.count) {
- margin-left: 0.5em;
- }
- }
- }
-
- > .other {
- margin-left: auto;
-
- > button {
- padding: 8px;
- margin: 0 8px;
-
- &:hover {
- color: var(--fgHighlighted);
- }
- }
- }
- }
-
- > .user {
- margin-top: 16px;
- padding: 16px 0 0 0;
- border-top: solid 0.5px var(--divider);
- display: flex;
- align-items: center;
-
- > .avatar {
- width: 52px;
- height: 52px;
- }
-
- > .name {
- margin: 0 0 0 12px;
- font-size: 90%;
- }
-
- > .koudoku {
- margin-left: auto;
- }
- }
-
- > .links {
- margin-top: 16px;
- padding: 24px 0 0 0;
- border-top: solid 0.5px var(--divider);
-
- > .link {
- margin-right: 0.75em;
- }
- }
- }
-
- > .footer {
- margin: var(--padding);
- font-size: 85%;
- opacity: 0.75;
- }
-}
-</style>
diff --git a/src/client/pages/pages.vue b/src/client/pages/pages.vue
deleted file mode 100644
index 6963682592..0000000000
--- a/src/client/pages/pages.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<template>
-<MkSpacer>
- <!-- TODO: MkHeaderใซ็ตฑๅˆ -->
- <MkTab v-model="tab" v-if="$i">
- <option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._pages.featured }}</option>
- <option value="my"><i class="fas fa-edit"></i> {{ $ts._pages.my }}</option>
- <option value="liked"><i class="fas fa-heart"></i> {{ $ts._pages.liked }}</option>
- </MkTab>
-
- <div class="_section">
- <div class="rknalgpo _content" v-if="tab === 'featured'">
- <MkPagination :pagination="featuredPagesPagination" #default="{items}">
- <MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/>
- </MkPagination>
- </div>
-
- <div class="rknalgpo _content my" v-if="tab === 'my'">
- <MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
- <MkPagination :pagination="myPagesPagination" #default="{items}">
- <MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/>
- </MkPagination>
- </div>
-
- <div class="rknalgpo _content" v-if="tab === 'liked'">
- <MkPagination :pagination="likedPagesPagination" #default="{items}">
- <MkPagePreview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/>
- </MkPagination>
- </div>
- </div>
-</MkSpacer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagePreview from '@client/components/page-preview.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkTab from '@client/components/tab.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkPagePreview, MkPagination, MkButton, MkTab
- },
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.pages,
- icon: 'fas fa-sticky-note',
- bg: 'var(--bg)',
- actions: [{
- icon: 'fas fa-plus',
- text: this.$ts.create,
- handler: this.create,
- }],
- },
- tab: 'featured',
- featuredPagesPagination: {
- endpoint: 'pages/featured',
- noPaging: true,
- },
- myPagesPagination: {
- endpoint: 'i/pages',
- limit: 5,
- },
- likedPagesPagination: {
- endpoint: 'i/page-likes',
- limit: 5,
- },
- };
- },
- methods: {
- create() {
- this.$router.push(`/pages/new`);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.rknalgpo {
- &.my .ckltabjg:first-child {
- margin-top: 16px;
- }
-
- .ckltabjg:not(:last-child) {
- margin-bottom: 8px;
- }
-
- @media (min-width: 500px) {
- .ckltabjg:not(:last-child) {
- margin-bottom: 16px;
- }
- }
-}
-</style>
diff --git a/src/client/pages/preview.vue b/src/client/pages/preview.vue
deleted file mode 100644
index 3df446e676..0000000000
--- a/src/client/pages/preview.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<template>
-<div class="graojtoi">
- <MkSample/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkSample from '@client/components/sample.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkSample,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.preview,
- icon: 'fas fa-eye',
- },
- }
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.graojtoi {
- padding: var(--margin);
-}
-</style>
diff --git a/src/client/pages/reset-password.vue b/src/client/pages/reset-password.vue
deleted file mode 100644
index 6dd9f24259..0000000000
--- a/src/client/pages/reset-password.vue
+++ /dev/null
@@ -1,69 +0,0 @@
-<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>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormGroup,
- FormLink,
- FormInput,
- FormButton,
- },
-
- props: {
- token: {
- type: String,
- required: false
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.resetPassword,
- icon: 'fas fa-lock'
- },
- password: '',
- }
- },
-
- mounted() {
- if (this.token == null) {
- os.popup(import('@client/components/forgot-password.vue'), {}, {}, 'closed');
- this.$router.push('/');
- }
- },
-
- methods: {
- async save() {
- await os.apiWithDialog('reset-password', {
- token: this.token,
- password: this.password,
- });
- this.$router.push('/');
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/reversi/game.board.vue b/src/client/pages/reversi/game.board.vue
deleted file mode 100644
index 0dd36faced..0000000000
--- a/src/client/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 class="turn" v-if="!iAmPlayer && !game.isEnded">
- <Mfm :key="'turn:' + turnUser().name" :text="$t('_reversi.turnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
- <MkEllipsis/>
- </p>
- <p class="turn" v-if="logPos != logs.length">
- <Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
- </p>
- <p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn()">{{ $ts._reversi.opponentTurn }}<MkEllipsis/></p>
- <p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn()" style="animation: tada 1s linear infinite both;">{{ $ts._reversi.myTurn }}</p>
- <p class="result" v-if="game.isEnded && logPos == logs.length">
- <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 class="labels-x" v-if="$store.state.gamesReversiShowBoardLabels">
- <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
- </div>
- <div class="flex">
- <div class="labels-y" v-if="$store.state.gamesReversiShowBoardLabels">
- <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 }"
- @click="set(i)"
- :title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`"
- >
- <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 class="labels-y" v-if="$store.state.gamesReversiShowBoardLabels">
- <div v-for="i in game.map.length">{{ i }}</div>
- </div>
- </div>
- <div class="labels-x" v-if="$store.state.gamesReversiShowBoardLabels">
- <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 class="actions" v-if="!game.isEnded && iAmPlayer">
- <MkButton @click="surrender" inline>{{ $ts._reversi.surrender }}</MkButton>
- </div>
-
- <div class="player" v-if="game.isEnded">
- <span>{{ logPos }} / {{ logs.length }}</span>
- <div class="buttons" v-if="!autoplaying">
- <MkButton inline @click="logPos = 0" :disabled="logPos == 0"><i class="fas fa-angle-double-left"></i></MkButton>
- <MkButton inline @click="logPos--" :disabled="logPos == 0"><i class="fas fa-angle-left"></i></MkButton>
- <MkButton inline @click="logPos++" :disabled="logPos == logs.length"><i class="fas fa-angle-right"></i></MkButton>
- <MkButton inline @click="logPos = logs.length" :disabled="logPos == logs.length"><i class="fas fa-angle-double-right"></i></MkButton>
- </div>
- <MkButton @click="autoplay()" :disabled="autoplaying" style="margin: var(--margin) auto 0 auto;"><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 '../../../games/reversi/core';
-import { url } from '@client/config';
-import MkButton from '@client/components/ui/button.vue';
-import { userPage } from '@client/filters/user';
-import * as os from '@client/os';
-import * as sound from '@client/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/src/client/pages/reversi/game.setting.vue b/src/client/pages/reversi/game.setting.vue
deleted file mode 100644
index eb6f24e4ab..0000000000
--- a/src/client/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 label="-Custom-" :value="mapName" v-if="mapName == '-Custom-'"/>
- <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 class="random" v-if="game.map == null"><i class="fas fa-dice"></i></div>
- <div class="board" v-else :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 class="card form _panel" v-if="form">
- <header>
- <span>{{ $ts._reversi.botSettings }}</span>
- </header>
-
- <div>
- <template v-for="item in form">
- <MkSwitch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</MkSwitch>
-
- <div class="card" v-if="item.type == 'radio'" :key="item.id">
- <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 class="card" v-if="item.type == 'slider'" :key="item.id">
- <header>
- <span>{{ item.label }}</span>
- </header>
-
- <div>
- <input type="range" :min="item.min" :max="item.max" :step="item.step || 1" v-model="item.value" @change="onChangeForm(item)"/>
- </div>
- </div>
-
- <div class="card" v-if="item.type == 'textbox'" :key="item.id">
- <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 inline primary @click="accept" v-if="!isAccepted">{{ $ts._reversi.ready }}</MkButton>
- <MkButton inline primary @click="cancel" v-if="isAccepted">{{ $ts._reversi.cancelReady }}</MkButton>
- </div>
- </footer>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as maps from '../../../games/reversi/maps';
-import MkButton from '@client/components/ui/button.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import MkRadio from '@client/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/src/client/pages/reversi/game.vue b/src/client/pages/reversi/game.vue
deleted file mode 100644
index ae10b45b5b..0000000000
--- a/src/client/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 '@client/os';
-import * as symbols from '@client/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/src/client/pages/reversi/index.vue b/src/client/pages/reversi/index.vue
deleted file mode 100644
index cedfd12089..0000000000
--- a/src/client/pages/reversi/index.vue
+++ /dev/null
@@ -1,279 +0,0 @@
-<template>
-<div class="bgvwxkhb" v-if="!matching">
- <h1>Misskey {{ $ts._reversi.reversi }}</h1>
-
- <div class="play">
- <MkButton primary round @click="match" style="margin: var(--margin) auto 0 auto;">{{ $ts.invite }}</MkButton>
- </div>
-
- <div class="_section">
- <MkFolder v-if="invitations.length > 0">
- <template #header>{{ $ts.invitations }}</template>
- <div class="nfcacttm">
- <button class="invitation _panel _button" v-for="invitation in invitations" 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 class="game _panel" v-for="g in myGames" tabindex="-1" :to="`/games/reversi/${g.id}`" :key="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 class="game _panel" v-for="g in games" tabindex="-1" :to="`/games/reversi/${g.id}`" :key="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 class="sazhgisb" v-else>
- <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 '@client/os';
-import MkButton from '@client/components/ui/button.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import * as symbols from '@client/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/src/client/pages/room/preview.vue b/src/client/pages/room/preview.vue
deleted file mode 100644
index 0cb6bcf04c..0000000000
--- a/src/client/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 '@client/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/src/client/pages/room/room.vue b/src/client/pages/room/room.vue
deleted file mode 100644
index 671dca3577..0000000000
--- a/src/client/pages/room/room.vue
+++ /dev/null
@@ -1,285 +0,0 @@
-<template>
-<div class="hveuntkp">
- <div class="controller _section" v-if="objectSelected">
- <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 @click="translate()" :primary="isTranslateMode"><i class="fas fa-arrows-alt"></i> {{ $ts._rooms.translate }}</MkButton>
- <MkButton inline @click="rotate()" :primary="isRotateMode"><i class="fas fa-undo"></i> {{ $ts._rooms.rotate }}</MkButton>
- <MkButton inline v-if="isTranslateMode || isRotateMode" @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 class="menu _section" v-if="isMyRoom">
- <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 '@client/scripts/room/room';
-import { parseAcct } from '@/misc/acct';
-import XPreview from './preview.vue';
-const storeItems = require('@client/scripts/room/furnitures.json5');
-import { query as urlQuery } from '../../../prelude/url';
-import MkButton from '@client/components/ui/button.vue';
-import MkSelect from '@client/components/form/select.vue';
-import { selectFile } from '@client/scripts/select-file';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import * as symbols from '@client/symbols';
-
-let room: Room;
-
-export default defineComponent({
- components: {
- XPreview,
- MkButton,
- MkSelect,
- },
-
- 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', {
- ...parseAcct(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'),
- });
- },
-
- beforeRouteLeave(to, from, next) {
- if (this.changed) {
- os.dialog({
- type: 'warning',
- text: this.$ts.leaveConfirm,
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) {
- next(false);
- } else {
- next();
- }
- });
- } else {
- next();
- }
- },
-
- 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.dialog({
- type: null,
- title: this.$ts._rooms.addFurniture,
- select: {
- items: storeItems.map(item => ({
- value: item.id, text: this.$t('_rooms._furnitures.' + item.id)
- }))
- },
- showCancelButton: true
- });
- 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.dialog({
- type: 'error',
- text: e.message
- });
- });
- },
-
- clear() {
- os.dialog({
- type: 'warning',
- text: this.$ts._rooms.clearConfirm,
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
- room.removeAllFurnitures();
- this.changed = true;
- });
- },
-
- chooseImage(key, e) {
- selectFile(e.currentTarget || e.target, null, false).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/src/client/pages/scratchpad.vue b/src/client/pages/scratchpad.vue
deleted file mode 100644
index 99164ec51f..0000000000
--- a/src/client/pages/scratchpad.vue
+++ /dev/null
@@ -1,149 +0,0 @@
-<template>
-<div class="iltifgqe">
- <div class="editor _panel _gap">
- <PrismEditor class="_code code" v-model="code" :highlight="highlighter" :line-numbers="false"/>
- <MkButton style="position: absolute; top: 8px; right: 8px;" @click="run()" primary><i class="fas fa-play"></i></MkButton>
- </div>
-
- <MkContainer :foldable="true" class="_gap">
- <template #header>{{ $ts.output }}</template>
- <div class="bepmlvbi">
- <div v-for="log in logs" class="log" :key="log.id" :class="{ print: log.print }">{{ log.text }}</div>
- </div>
- </MkContainer>
-
- <div class="_gap">
- {{ $ts.scratchpadDescription }}
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import 'prismjs';
-import { highlight, languages } from 'prismjs/components/prism-core';
-import 'prismjs/components/prism-clike';
-import 'prismjs/components/prism-javascript';
-import 'prismjs/themes/prism-okaidia.css';
-import { PrismEditor } from 'vue-prism-editor';
-import 'vue-prism-editor/dist/prismeditor.min.css';
-import { AiScript, parse, utils, values } from '@syuilo/aiscript';
-import MkContainer from '@client/components/ui/container.vue';
-import MkButton from '@client/components/ui/button.vue';
-import { createAiScriptEnv } from '@client/scripts/aiscript/api';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkContainer,
- MkButton,
- PrismEditor,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.scratchpad,
- icon: 'fas fa-terminal',
- },
- code: '',
- logs: [],
- }
- },
-
- watch: {
- code() {
- localStorage.setItem('scratchpad', this.code);
- }
- },
-
- created() {
- const saved = localStorage.getItem('scratchpad');
- if (saved) {
- this.code = saved;
- }
- },
-
- methods: {
- async run() {
- this.logs = [];
- const aiscript = new AiScript(createAiScriptEnv({
- storageKey: 'scratchpad',
- token: this.$i?.token,
- }), {
- in: (q) => {
- return new Promise(ok => {
- os.dialog({
- title: q,
- input: {}
- }).then(({ canceled, result: a }) => {
- ok(a);
- });
- });
- },
- out: (value) => {
- this.logs.push({
- id: Math.random(),
- text: value.type === 'str' ? value.value : utils.valToString(value),
- print: true
- });
- },
- log: (type, params) => {
- switch (type) {
- case 'end': this.logs.push({
- id: Math.random(),
- text: utils.valToString(params.val, true),
- print: false
- }); break;
- default: break;
- }
- }
- });
-
- let ast;
- try {
- ast = parse(this.code);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: 'Syntax error :('
- });
- return;
- }
- try {
- await aiscript.exec(ast);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: e
- });
- }
- },
-
- highlighter(code) {
- return highlight(code, languages.js, 'javascript');
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.iltifgqe {
- padding: 16px;
-
- > .editor {
- position: relative;
- }
-}
-
-.bepmlvbi {
- padding: 16px;
-
- > .log {
- &:not(.print) {
- opacity: 0.7;
- }
- }
-}
-</style>
diff --git a/src/client/pages/search.vue b/src/client/pages/search.vue
deleted file mode 100644
index 8cf4d32a8f..0000000000
--- a/src/client/pages/search.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-<template>
-<div class="_section">
- <div class="_content">
- <XNotes ref="notes" :pagination="pagination" @before="before" @after="after"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import XNotes from '@client/components/notes.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XNotes
- },
-
- 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,
- })
- },
- };
- },
-
- watch: {
- $route() {
- (this.$refs.notes as any).reload();
- }
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/2fa.vue b/src/client/pages/settings/2fa.vue
deleted file mode 100644
index 386e7c635a..0000000000
--- a/src/client/pages/settings/2fa.vue
+++ /dev/null
@@ -1,247 +0,0 @@
-<template>
-<section class="_card">
- <div class="_title"><i class="fas fa-lock"></i> {{ $ts.twoStepAuthentication }}</div>
- <div class="_content">
- <MkButton v-if="!data && !$i.twoFactorEnabled" @click="register">{{ $ts._2fa.registerDevice }}</MkButton>
- <template v-if="$i.twoFactorEnabled">
- <p>{{ $ts._2fa.alreadyRegistered }}</p>
- <MkButton @click="unregister">{{ $ts.unregister }}</MkButton>
-
- <template v-if="supportsCredentials">
- <hr class="totp-method-sep">
-
- <h2 class="heading">{{ $ts.securityKey }}</h2>
- <p>{{ $ts._2fa.securityKeyInfo }}</p>
- <div class="key-list">
- <div class="key" v-for="key in $i.securityKeysList">
- <h3>{{ key.name }}</h3>
- <div class="last-used">{{ $ts.lastUsed }}<MkTime :time="key.lastUsed"/></div>
- <MkButton @click="unregisterKey(key)">{{ $ts.unregister }}</MkButton>
- </div>
- </div>
-
- <MkSwitch v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin" v-if="$i.securityKeysList.length > 0">{{ $ts.passwordLessLogin }}</MkSwitch>
-
- <MkInfo warn v-if="registration && registration.error">{{ $ts.error }} {{ registration.error }}</MkInfo>
- <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ $ts._2fa.registerKey }}</MkButton>
-
- <ol v-if="registration && !registration.error">
- <li v-if="registration.stage >= 0">
- {{ $ts.tapSecurityKey }}
- <i v-if="registration.saving && registration.stage == 0" class="fas fa-spinner fa-pulse fa-fw"></i>
- </li>
- <li v-if="registration.stage >= 1">
- <MkForm :disabled="registration.stage != 1 || registration.saving">
- <MkInput v-model="keyName" :max="30">
- <template #label>{{ $ts.securityKeyName }}</template>
- </MkInput>
- <MkButton @click="registerKey" :disabled="keyName.length == 0">{{ $ts.registerSecurityKey }}</MkButton>
- <i v-if="registration.saving && registration.stage == 1" class="fas fa-spinner fa-pulse fa-fw"></i>
- </MkForm>
- </li>
- </ol>
- </template>
- </template>
- <div v-if="data && !$i.twoFactorEnabled">
- <ol style="margin: 0; padding: 0 0 0 1em;">
- <li>
- <I18n :src="$ts._2fa.step1" tag="span">
- <template #a>
- <a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
- </template>
- <template #b>
- <a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
- </template>
- </I18n>
- </li>
- <li>{{ $ts._2fa.step2 }}<br><img :src="data.qr"></li>
- <li>{{ $ts._2fa.step3 }}<br>
- <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ $ts.token }}</template></MkInput>
- <MkButton primary @click="submit">{{ $ts.done }}</MkButton>
- </li>
- </ol>
- <MkInfo>{{ $ts._2fa.step4 }}</MkInfo>
- </div>
- </div>
-</section>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { hostname } from '@client/config';
-import { byteify, hexify, stringify } from '@client/scripts/2fa';
-import MkButton from '@client/components/ui/button.vue';
-import MkInfo from '@client/components/ui/info.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- MkButton, MkInfo, MkInput, MkSwitch
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.twoStepAuthentication,
- icon: 'fas fa-lock'
- },
- data: null,
- supportsCredentials: !!navigator.credentials,
- usePasswordLessLogin: this.$i.usePasswordLessLogin,
- registration: null,
- keyName: '',
- token: null,
- };
- },
-
- methods: {
- register() {
- os.dialog({
- title: this.$ts.password,
- input: {
- type: 'password'
- }
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- os.api('i/2fa/register', {
- password: password
- }).then(data => {
- this.data = data;
- });
- });
- },
-
- unregister() {
- os.dialog({
- title: this.$ts.password,
- input: {
- type: 'password'
- }
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- os.api('i/2fa/unregister', {
- password: password
- }).then(() => {
- this.usePasswordLessLogin = false;
- this.updatePasswordLessLogin();
- }).then(() => {
- os.success();
- this.$i.twoFactorEnabled = false;
- });
- });
- },
-
- submit() {
- os.api('i/2fa/done', {
- token: this.token
- }).then(() => {
- os.success();
- this.$i.twoFactorEnabled = true;
- }).catch(e => {
- os.dialog({
- type: 'error',
- text: e
- });
- });
- },
-
- registerKey() {
- this.registration.saving = true;
- os.api('i/2fa/key-done', {
- password: this.registration.password,
- name: this.keyName,
- challengeId: this.registration.challengeId,
- // we convert each 16 bits to a string to serialise
- clientDataJSON: stringify(this.registration.credential.response.clientDataJSON),
- attestationObject: hexify(this.registration.credential.response.attestationObject)
- }).then(key => {
- this.registration = null;
- key.lastUsed = new Date();
- os.success();
- })
- },
-
- unregisterKey(key) {
- os.dialog({
- title: this.$ts.password,
- input: {
- type: 'password'
- }
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- return os.api('i/2fa/remove-key', {
- password,
- credentialId: key.id
- }).then(() => {
- this.usePasswordLessLogin = false;
- this.updatePasswordLessLogin();
- }).then(() => {
- os.success();
- });
- });
- },
-
- addSecurityKey() {
- os.dialog({
- title: this.$ts.password,
- input: {
- type: 'password'
- }
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- os.api('i/2fa/register-key', {
- password
- }).then(registration => {
- this.registration = {
- password,
- challengeId: registration.challengeId,
- stage: 0,
- publicKeyOptions: {
- challenge: byteify(registration.challenge, 'base64'),
- rp: {
- id: hostname,
- name: 'Misskey'
- },
- user: {
- id: byteify(this.$i.id, 'ascii'),
- name: this.$i.username,
- displayName: this.$i.name,
- },
- pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
- timeout: 60000,
- attestation: 'direct'
- },
- saving: true
- };
- return navigator.credentials.create({
- publicKey: this.registration.publicKeyOptions
- });
- }).then(credential => {
- this.registration.credential = credential;
- this.registration.saving = false;
- this.registration.stage = 1;
- }).catch(err => {
- console.warn('Error while registering?', err);
- this.registration.error = err.message;
- this.registration.stage = -1;
- });
- });
- },
-
- updatePasswordLessLogin() {
- os.api('i/2fa/password-less', {
- value: !!this.usePasswordLessLogin
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/account-info.vue b/src/client/pages/settings/account-info.vue
deleted file mode 100644
index 16ce91b12f..0000000000
--- a/src/client/pages/settings/account-info.vue
+++ /dev/null
@@ -1,185 +0,0 @@
-<template>
-<FormBase>
- <FormKeyValueView>
- <template #key>ID</template>
- <template #value><span class="_monospace">{{ $i.id }}</span></template>
- </FormKeyValueView>
-
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts.registeredDate }}</template>
- <template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
- </FormKeyValueView>
- </FormGroup>
-
- <FormGroup v-if="stats">
- <template #label>{{ $ts.statistics }}</template>
- <FormKeyValueView>
- <template #key>{{ $ts.notesCount }}</template>
- <template #value>{{ number(stats.notesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.repliesCount }}</template>
- <template #value>{{ number(stats.repliesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.renotesCount }}</template>
- <template #value>{{ number(stats.renotesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.repliedCount }}</template>
- <template #value>{{ number(stats.repliedCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.renotedCount }}</template>
- <template #value>{{ number(stats.renotedCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.pollVotesCount }}</template>
- <template #value>{{ number(stats.pollVotesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.pollVotedCount }}</template>
- <template #value>{{ number(stats.pollVotedCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.sentReactionsCount }}</template>
- <template #value>{{ number(stats.sentReactionsCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.receivedReactionsCount }}</template>
- <template #value>{{ number(stats.receivedReactionsCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.noteFavoritesCount }}</template>
- <template #value>{{ number(stats.noteFavoritesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.followingCount }}</template>
- <template #value>{{ number(stats.followingCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template>
- <template #value>{{ number(stats.localFollowingCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template>
- <template #value>{{ number(stats.remoteFollowingCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.followersCount }}</template>
- <template #value>{{ number(stats.followersCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template>
- <template #value>{{ number(stats.localFollowersCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template>
- <template #value>{{ number(stats.remoteFollowersCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.pageLikesCount }}</template>
- <template #value>{{ number(stats.pageLikesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.pageLikedCount }}</template>
- <template #value>{{ number(stats.pageLikedCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.driveFilesCount }}</template>
- <template #value>{{ number(stats.driveFilesCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <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>
-
- <FormGroup>
- <template #label>{{ $ts.other }}</template>
- <FormKeyValueView>
- <template #key>emailVerified</template>
- <template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>twoFactorEnabled</template>
- <template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>securityKeys</template>
- <template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>usePasswordLessLogin</template>
- <template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>isModerator</template>
- <template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>isAdmin</template>
- <template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template>
- </FormKeyValueView>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-import bytes from '@client/filters/bytes';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSelect,
- FormSwitch,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.accountInfo,
- icon: 'fas fa-info-circle'
- },
- stats: null
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
- os.api('users/stats', {
- userId: this.$i.id
- }).then(stats => {
- this.stats = stats;
- });
- },
-
- methods: {
- number,
- bytes,
- }
-});
-</script>
diff --git a/src/client/pages/settings/accounts.vue b/src/client/pages/settings/accounts.vue
deleted file mode 100644
index d2966cc216..0000000000
--- a/src/client/pages/settings/accounts.vue
+++ /dev/null
@@ -1,149 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormButton @click="addAccount" primary><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton>
-
- <div class="_debobigegoItem _button" v-for="account in accounts" :key="account.id" @click="menu(account, $event)">
- <div class="_debobigegoPanel lcjjdxlm">
- <div class="avatar">
- <MkAvatar :user="account" class="avatar"/>
- </div>
- <div class="body">
- <div class="name">
- <MkUserName :user="account"/>
- </div>
- <div class="acct">
- <MkAcct :user="account"/>
- </div>
- </div>
- </div>
- </div>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { getAccounts, addAccount, login } from '@client/account';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSuspense,
- FormButton,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.accounts,
- icon: 'fas fa-users',
- bg: 'var(--bg)',
- },
- storedAccounts: getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)),
- accounts: null,
- init: async () => os.api('users/show', {
- userIds: (await this.storedAccounts).map(x => x.id)
- }).then(accounts => {
- this.accounts = accounts;
- }),
- };
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- menu(account, ev) {
- os.popupMenu([{
- text: this.$ts.switch,
- icon: 'fas fa-exchange-alt',
- action: () => this.switchAccount(account),
- }, {
- text: this.$ts.remove,
- icon: 'fas fa-trash-alt',
- danger: true,
- action: () => this.removeAccount(account),
- }], ev.currentTarget || ev.target);
- },
-
- addAccount(ev) {
- os.popupMenu([{
- text: this.$ts.existingAccount,
- action: () => { this.addExistingAccount(); },
- }, {
- text: this.$ts.createAccount,
- action: () => { this.createAccount(); },
- }], ev.currentTarget || ev.target);
- },
-
- addExistingAccount() {
- os.popup(import('@client/components/signin-dialog.vue'), {}, {
- done: res => {
- addAccount(res.id, res.i);
- os.success();
- },
- }, 'closed');
- },
-
- createAccount() {
- os.popup(import('@client/components/signup-dialog.vue'), {}, {
- done: res => {
- addAccount(res.id, res.i);
- this.switchAccountWithToken(res.i);
- },
- }, 'closed');
- },
-
- async switchAccount(account: any) {
- const storedAccounts = await getAccounts();
- const token = storedAccounts.find(x => x.id === account.id).token;
- this.switchAccountWithToken(token);
- },
-
- switchAccountWithToken(token: string) {
- login(token);
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.lcjjdxlm {
- display: flex;
- padding: 16px;
-
- > .avatar {
- display: block;
- flex-shrink: 0;
- margin: 0 12px 0 0;
-
- > .avatar {
- width: 50px;
- height: 50px;
- }
- }
-
- > .body {
- display: flex;
- flex-direction: column;
- justify-content: center;
- width: calc(100% - 62px);
- position: relative;
-
- > .name {
- font-weight: bold;
- }
- }
-}
-</style>
diff --git a/src/client/pages/settings/api.vue b/src/client/pages/settings/api.vue
deleted file mode 100644
index 5c7496e2f9..0000000000
--- a/src/client/pages/settings/api.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<FormBase>
- <FormButton @click="generateToken" primary>{{ $ts.generateAccessToken }}</FormButton>
- <FormLink to="/settings/apps">{{ $ts.manageAccessTokens }}</FormLink>
- <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- FormLink,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'API',
- icon: 'fas fa-key',
- bg: 'var(--bg)',
- },
- isDesktop: window.innerWidth >= 1100,
- };
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- generateToken() {
- os.popup(import('@client/components/token-generate-window.vue'), {}, {
- done: async result => {
- const { name, permissions } = result;
- const { token } = await os.api('miauth/gen-token', {
- session: null,
- name: name,
- permission: permissions,
- });
-
- os.dialog({
- type: 'success',
- title: this.$ts.token,
- text: token
- });
- },
- }, 'closed');
- },
- }
-});
-</script>
diff --git a/src/client/pages/settings/apps.vue b/src/client/pages/settings/apps.vue
deleted file mode 100644
index da4f672adf..0000000000
--- a/src/client/pages/settings/apps.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-<template>
-<FormBase>
- <FormPagination :pagination="pagination" ref="list">
- <template #empty>
- <div class="_fullinfo">
- <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ $ts.nothing }}</div>
- </div>
- </template>
- <template #default="{items}">
- <div class="_debobigegoPanel bfomjevm" v-for="token in items" :key="token.id">
- <img class="icon" :src="token.iconUrl" alt="" v-if="token.iconUrl"/>
- <div class="body">
- <div class="name">{{ token.name }}</div>
- <div class="description">{{ token.description }}</div>
- <div class="_keyValue">
- <div>{{ $ts.installedDate }}:</div>
- <div><MkTime :time="token.createdAt"/></div>
- </div>
- <div class="_keyValue">
- <div>{{ $ts.lastUsedDate }}:</div>
- <div><MkTime :time="token.lastUsedAt"/></div>
- </div>
- <div class="actions">
- <button class="_button" @click="revoke(token)"><i class="fas fa-trash-alt"></i></button>
- </div>
- <details>
- <summary>{{ $ts.details }}</summary>
- <ul>
- <li v-for="p in token.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
- </ul>
- </details>
- </div>
- </div>
- </template>
- </FormPagination>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormPagination from '@client/components/debobigego/pagination.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormPagination,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.installedApps,
- icon: 'fas fa-plug',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'i/apps',
- limit: 100,
- params: {
- sort: '+lastUsedAt'
- }
- },
- };
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- revoke(token) {
- os.api('i/revoke-token', { tokenId: token.id }).then(() => {
- this.$refs.list.reload();
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.bfomjevm {
- display: flex;
- padding: 16px;
-
- > .icon {
- display: block;
- flex-shrink: 0;
- margin: 0 12px 0 0;
- width: 50px;
- height: 50px;
- border-radius: 8px;
- }
-
- > .body {
- width: calc(100% - 62px);
- position: relative;
-
- > .name {
- font-weight: bold;
- }
- }
-}
-</style>
diff --git a/src/client/pages/settings/custom-css.vue b/src/client/pages/settings/custom-css.vue
deleted file mode 100644
index fd473a11fa..0000000000
--- a/src/client/pages/settings/custom-css.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<template>
-<FormBase>
- <FormInfo warn>{{ $ts.customCssWarn }}</FormInfo>
-
- <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;">
- <span>{{ $ts.local }}</span>
- </FormTextarea>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormTextarea from '@client/components/form/textarea.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormRadios from '@client/components/form/radios.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import { unisonReload } from '@client/scripts/unison-reload';
-import * as symbols from '@client/symbols';
-import { defaultStore } from '@client/store';
-
-export default defineComponent({
- components: {
- FormTextarea,
- FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
- FormLink,
- FormButton,
- FormInfo,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.customCss,
- icon: 'fas fa-code',
- bg: 'var(--bg)',
- },
- localCustomCss: localStorage.getItem('customCss')
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
- this.$watch('localCustomCss', this.apply);
- },
-
- methods: {
- async apply() {
- localStorage.setItem('customCss', this.localCustomCss);
-
- const { canceled } = await os.dialog({
- type: 'info',
- text: this.$ts.reloadToApplySetting,
- showCancelButton: true
- });
- if (canceled) return;
-
- unisonReload();
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/deck.vue b/src/client/pages/settings/deck.vue
deleted file mode 100644
index e4b5c697c4..0000000000
--- a/src/client/pages/settings/deck.vue
+++ /dev/null
@@ -1,107 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <template #label>{{ $ts.defaultNavigationBehaviour }}</template>
- <FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch>
- </FormGroup>
-
- <FormSwitch v-model="alwaysShowMainColumn">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch>
-
- <FormRadios v-model="columnAlign">
- <template #desc>{{ $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>
- <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>
- <template #suffix>px</template>
- </FormInput>
-
- <FormLink @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormRadios from '@client/components/debobigego/radios.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import { deckStore } from '@client/ui/deck/deck-store';
-import * as os from '@client/os';
-import { unisonReload } from '@client/scripts/unison-reload';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormLink,
- FormInput,
- FormRadios,
- FormBase,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.deck,
- icon: 'fas fa-columns',
- bg: 'var(--bg)',
- },
- }
- },
-
- computed: {
- navWindow: deckStore.makeGetterSetter('navWindow'),
- alwaysShowMainColumn: deckStore.makeGetterSetter('alwaysShowMainColumn'),
- columnAlign: deckStore.makeGetterSetter('columnAlign'),
- columnMargin: deckStore.makeGetterSetter('columnMargin'),
- columnHeaderHeight: deckStore.makeGetterSetter('columnHeaderHeight'),
- profile: deckStore.makeGetterSetter('profile'),
- },
-
- watch: {
- async navWindow() {
- const { canceled } = await os.dialog({
- type: 'info',
- text: this.$ts.reloadToApplySetting,
- showCancelButton: true
- });
- if (canceled) return;
-
- unisonReload();
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async setProfile() {
- const { canceled, result: name } = await os.dialog({
- title: this.$ts._deck.profile,
- input: {
- allowEmpty: false
- }
- });
- if (canceled) return;
- this.profile = name;
- unisonReload();
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/delete-account.vue b/src/client/pages/settings/delete-account.vue
deleted file mode 100644
index 6bac214e04..0000000000
--- a/src/client/pages/settings/delete-account.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-<template>
-<FormBase>
- <FormInfo warn>{{ $ts._accountDelete.mayTakeTime }}</FormInfo>
- <FormInfo>{{ $ts._accountDelete.sendEmail }}</FormInfo>
- <FormButton @click="deleteAccount" danger v-if="!$i.isDeleted">{{ $ts._accountDelete.requestAccountDelete }}</FormButton>
- <FormButton disabled v-else>{{ $ts._accountDelete.inProgress }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import { debug } from '@client/config';
-import { signout } from '@client/account';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- FormGroup,
- FormInfo,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts._accountDelete.accountDelete,
- icon: 'fas fa-exclamation-triangle',
- bg: 'var(--bg)',
- },
- debug,
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async deleteAccount() {
- const { canceled, result: password } = await os.dialog({
- title: this.$ts.password,
- input: {
- type: 'password'
- }
- });
- if (canceled) return;
-
- await os.apiWithDialog('i/delete-account', {
- password: password
- });
-
- await os.dialog({
- title: this.$ts._accountDelete.started,
- });
-
- signout();
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/drive.vue b/src/client/pages/settings/drive.vue
deleted file mode 100644
index 2d73eb4df7..0000000000
--- a/src/client/pages/settings/drive.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<template>
-<FormBase class="">
- <FormGroup v-if="!fetching">
- <template #label>{{ $ts.usageAmount }}</template>
- <div class="_debobigegoItem uawsfosz">
- <div class="_debobigegoPanel">
- <div class="meter"><div :style="meterStyle"></div></div>
- </div>
- </div>
- <FormKeyValueView>
- <template #key>{{ $ts.capacity }}</template>
- <template #value>{{ bytes(capacity, 1) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>{{ $ts.inUse }}</template>
- <template #value>{{ bytes(usage, 1) }}</template>
- </FormKeyValueView>
- </FormGroup>
-
- <div class="_debobigegoItem">
- <div class="_debobigegoLabel">{{ $ts.statistics }}</div>
- <div class="_debobigegoPanel">
- <div ref="chart"></div>
- </div>
- </div>
-
- <FormButton :center="false" @click="chooseUploadFolder()" primary>
- {{ $ts.uploadFolder }}
- <template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
- <template #suffixIcon><i class="fas fa-folder-open"></i></template>
- </FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as tinycolor from 'tinycolor2';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import * as os from '@client/os';
-import bytes from '@client/filters/bytes';
-import * as symbols from '@client/symbols';
-
-// TODO: render chart
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- FormGroup,
- FormKeyValueView,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.drive,
- icon: 'fas fa-cloud',
- bg: 'var(--bg)',
- },
- fetching: true,
- usage: null,
- capacity: null,
- uploadFolder: null,
- }
- },
-
- computed: {
- meterStyle(): any {
- return {
- width: `${this.usage / this.capacity * 100}%`,
- background: tinycolor({
- h: 180 - (this.usage / this.capacity * 180),
- s: 0.7,
- l: 0.5
- })
- };
- }
- },
-
- async created() {
- os.api('drive').then(info => {
- this.capacity = info.capacity;
- this.usage = info.usage;
- this.fetching = false;
- this.$nextTick(() => {
- this.renderChart();
- });
- });
-
- if (this.$store.state.uploadFolder) {
- this.uploadFolder = await os.api('drive/folders/show', {
- folderId: this.$store.state.uploadFolder
- });
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- chooseUploadFolder() {
- os.selectDriveFolder(false).then(async folder => {
- this.$store.set('uploadFolder', folder ? folder.id : null);
- os.success();
- if (this.$store.state.uploadFolder) {
- this.uploadFolder = await os.api('drive/folders/show', {
- folderId: this.$store.state.uploadFolder
- });
- } else {
- this.uploadFolder = null;
- }
- });
- },
-
- bytes
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-@use "sass:math";
-
-.uawsfosz {
- > div {
- padding: 24px;
-
- > .meter {
- $size: 12px;
- background: rgba(0, 0, 0, 0.1);
- border-radius: math.div($size, 2);
- overflow: hidden;
-
- > div {
- height: $size;
- border-radius: math.div($size, 2);
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/settings/email-address.vue b/src/client/pages/settings/email-address.vue
deleted file mode 100644
index f98b22ada7..0000000000
--- a/src/client/pages/settings/email-address.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <FormInput v-model="emailAddress" type="email">
- {{ $ts.emailAddress }}
- <template #desc v-if="$i.email && !$i.emailVerified">{{ $ts.verificationEmailSent }}</template>
- <template #desc v-else-if="emailAddress === $i.email && $i.emailVerified">{{ $ts.emailVerified }}</template>
- </FormInput>
- </FormGroup>
- <FormButton @click="save" primary>{{ $ts.save }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormInput from '@client/components/form/input.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormInput,
- FormButton,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.emailAddress,
- icon: 'fas fa-envelope',
- bg: 'var(--bg)',
- },
- emailAddress: null,
- code: null,
- }
- },
-
- created() {
- this.emailAddress = this.$i.email;
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- save() {
- os.dialog({
- title: this.$ts.password,
- input: {
- type: 'password'
- }
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- os.apiWithDialog('i/update-email', {
- password: password,
- email: this.emailAddress,
- });
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/email-notification.vue b/src/client/pages/settings/email-notification.vue
deleted file mode 100644
index 1b78621c3f..0000000000
--- a/src/client/pages/settings/email-notification.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <FormSwitch v-model="mention">
- {{ $ts._notification._types.mention }}
- </FormSwitch>
- <FormSwitch v-model="reply">
- {{ $ts._notification._types.reply }}
- </FormSwitch>
- <FormSwitch v-model="quote">
- {{ $ts._notification._types.quote }}
- </FormSwitch>
- <FormSwitch v-model="follow">
- {{ $ts._notification._types.follow }}
- </FormSwitch>
- <FormSwitch v-model="receiveFollowRequest">
- {{ $ts._notification._types.receiveFollowRequest }}
- </FormSwitch>
- <FormSwitch v-model="groupInvited">
- {{ $ts._notification._types.groupInvited }}
- </FormSwitch>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSwitch,
- FormButton,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.emailNotification,
- icon: 'fas fa-envelope',
- bg: 'var(--bg)',
- },
-
- mention: this.$i.emailNotificationTypes.includes('mention'),
- reply: this.$i.emailNotificationTypes.includes('reply'),
- quote: this.$i.emailNotificationTypes.includes('quote'),
- follow: this.$i.emailNotificationTypes.includes('follow'),
- receiveFollowRequest: this.$i.emailNotificationTypes.includes('receiveFollowRequest'),
- groupInvited: this.$i.emailNotificationTypes.includes('groupInvited'),
- }
- },
-
- created() {
- this.$watch('mention', this.save);
- this.$watch('reply', this.save);
- this.$watch('quote', this.save);
- this.$watch('follow', this.save);
- this.$watch('receiveFollowRequest', this.save);
- this.$watch('groupInvited', this.save);
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- save() {
- os.api('i/update', {
- emailNotificationTypes: [
- ...[this.mention ? 'mention' : null],
- ...[this.reply ? 'reply' : null],
- ...[this.quote ? 'quote' : null],
- ...[this.follow ? 'follow' : null],
- ...[this.receiveFollowRequest ? 'receiveFollowRequest' : null],
- ...[this.groupInvited ? 'groupInvited' : null],
- ].filter(x => x != null)
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/email.vue b/src/client/pages/settings/email.vue
deleted file mode 100644
index adc62133ac..0000000000
--- a/src/client/pages/settings/email.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <template #label>{{ $ts.emailAddress }}</template>
- <FormLink to="/settings/email/address">
- <template v-if="$i.email && !$i.emailVerified" #icon><i class="fas fa-exclamation-triangle" style="color: var(--warn);"></i></template>
- <template v-else-if="$i.email && $i.emailVerified" #icon><i class="fas fa-check" style="color: var(--success);"></i></template>
- {{ $i.email || $ts.notSet }}
- </FormLink>
- </FormGroup>
-
- <FormLink to="/settings/email/notification">
- <template #icon><i class="fas fa-bell"></i></template>
- {{ $ts.emailNotification }}
- </FormLink>
-
- <FormSwitch :value="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail">
- {{ $ts.receiveAnnouncementFromInstance }}
- </FormSwitch>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormLink,
- FormButton,
- FormSwitch,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.email,
- icon: 'fas fa-envelope',
- bg: 'var(--bg)',
- },
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- onChangeReceiveAnnouncementEmail(v) {
- os.api('i/update', {
- receiveAnnouncementEmail: v
- });
- },
- }
-});
-</script>
diff --git a/src/client/pages/settings/experimental-features.vue b/src/client/pages/settings/experimental-features.vue
deleted file mode 100644
index 971c45a628..0000000000
--- a/src/client/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 '@client/components/form/switch.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/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/src/client/pages/settings/general.vue b/src/client/pages/settings/general.vue
deleted file mode 100644
index 59dd251948..0000000000
--- a/src/client/pages/settings/general.vue
+++ /dev/null
@@ -1,223 +0,0 @@
-<template>
-<FormBase>
- <FormSwitch v-model="showFixedPostForm">{{ $ts.showFixedPostForm }}</FormSwitch>
-
- <FormSelect v-model="lang">
- <template #label>{{ $ts.uiLanguage }}</template>
- <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
- <template #caption>
- <I18n :src="$ts.i18nInfo" tag="span">
- <template #link>
- <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
- </template>
- </I18n>
- </template>
- </FormSelect>
-
- <FormGroup>
- <template #label>{{ $ts.behavior }}</template>
- <FormSwitch v-model="imageNewTab">{{ $ts.openImageInNewTab }}</FormSwitch>
- <FormSwitch v-model="enableInfiniteScroll">{{ $ts.enableInfiniteScroll }}</FormSwitch>
- <FormSwitch v-model="useReactionPickerForContextMenu">{{ $ts.useReactionPickerForContextMenu }}</FormSwitch>
- <FormSwitch v-model="disablePagesScript">{{ $ts.disablePagesScript }}</FormSwitch>
- </FormGroup>
-
- <FormSelect v-model="serverDisconnectedBehavior">
- <template #label>{{ $ts.whenServerDisconnected }}</template>
- <option value="reload">{{ $ts._serverDisconnectedBehavior.reload }}</option>
- <option value="dialog">{{ $ts._serverDisconnectedBehavior.dialog }}</option>
- <option value="quiet">{{ $ts._serverDisconnectedBehavior.quiet }}</option>
- </FormSelect>
-
- <FormGroup>
- <template #label>{{ $ts.appearance }}</template>
- <FormSwitch v-model="disableAnimatedMfm">{{ $ts.disableAnimatedMfm }}</FormSwitch>
- <FormSwitch v-model="reduceAnimation">{{ $ts.reduceUiAnimation }}</FormSwitch>
- <FormSwitch v-model="useBlurEffect">{{ $ts.useBlurEffect }}</FormSwitch>
- <FormSwitch v-model="useBlurEffectForModal">{{ $ts.useBlurEffectForModal }}</FormSwitch>
- <FormSwitch v-model="showGapBetweenNotesInTimeline">{{ $ts.showGapBetweenNotesInTimeline }}</FormSwitch>
- <FormSwitch v-model="loadRawImages">{{ $ts.loadRawImages }}</FormSwitch>
- <FormSwitch v-model="disableShowingAnimatedImages">{{ $ts.disableShowingAnimatedImages }}</FormSwitch>
- <FormSwitch v-model="squareAvatars">{{ $ts.squareAvatars }}</FormSwitch>
- <FormSwitch v-model="useSystemFont">{{ $ts.useSystemFont }}</FormSwitch>
- <FormSwitch v-model="useOsNativeEmojis">{{ $ts.useOsNativeEmojis }}
- <div><Mfm text="๐Ÿฎ๐Ÿฆ๐Ÿญ๐Ÿฉ๐Ÿฐ๐Ÿซ๐Ÿฌ๐Ÿฅž๐Ÿช" :key="useOsNativeEmojis"/></div>
- </FormSwitch>
- </FormGroup>
-
- <FormGroup>
- <FormSwitch v-model="aiChanMode">{{ $ts.aiChanMode }}</FormSwitch>
- </FormGroup>
-
- <FormRadios v-model="fontSize">
- <template #desc>{{ $ts.fontSize }}</template>
- <option value="small"><span style="font-size: 14px;">Aa</span></option>
- <option :value="null"><span style="font-size: 16px;">Aa</span></option>
- <option value="large"><span style="font-size: 18px;">Aa</span></option>
- <option value="veryLarge"><span style="font-size: 20px;">Aa</span></option>
- </FormRadios>
-
- <FormSelect v-model="instanceTicker">
- <template #label>{{ $ts.instanceTicker }}</template>
- <option value="none">{{ $ts._instanceTicker.none }}</option>
- <option value="remote">{{ $ts._instanceTicker.remote }}</option>
- <option value="always">{{ $ts._instanceTicker.always }}</option>
- </FormSelect>
-
- <FormSelect v-model="nsfw">
- <template #label>{{ $ts.nsfw }}</template>
- <option value="respect">{{ $ts._nsfw.respect }}</option>
- <option value="ignore">{{ $ts._nsfw.ignore }}</option>
- <option value="force">{{ $ts._nsfw.force }}</option>
- </FormSelect>
-
- <FormGroup>
- <template #label>{{ $ts.defaultNavigationBehaviour }}</template>
- <FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch>
- </FormGroup>
-
- <FormSelect v-model="chatOpenBehavior">
- <template #label>{{ $ts.chatOpenBehavior }}</template>
- <option value="page">{{ $ts.showInPage }}</option>
- <option value="window">{{ $ts.openInWindow }}</option>
- <option value="popout">{{ $ts.popout }}</option>
- </FormSelect>
-
- <FormLink to="/settings/deck">{{ $ts.deck }}</FormLink>
-
- <FormLink to="/settings/custom-css"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormSelect from '@client/components/debobigego/select.vue';
-import FormRadios from '@client/components/debobigego/radios.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import MkLink from '@client/components/link.vue';
-import { langs } from '@client/config';
-import { defaultStore } from '@client/store';
-import { ColdDeviceStorage } from '@client/store';
-import * as os from '@client/os';
-import { unisonReload } from '@client/scripts/unison-reload';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkLink,
- FormSwitch,
- FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
- FormLink,
- FormButton,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.general,
- icon: 'fas fa-cogs',
- bg: 'var(--bg)'
- },
- langs,
- lang: localStorage.getItem('lang'),
- fontSize: localStorage.getItem('fontSize'),
- useSystemFont: localStorage.getItem('useSystemFont') != null,
- }
- },
-
- computed: {
- serverDisconnectedBehavior: defaultStore.makeGetterSetter('serverDisconnectedBehavior'),
- reduceAnimation: defaultStore.makeGetterSetter('animation', v => !v, v => !v),
- useBlurEffectForModal: defaultStore.makeGetterSetter('useBlurEffectForModal'),
- useBlurEffect: defaultStore.makeGetterSetter('useBlurEffect'),
- showGapBetweenNotesInTimeline: defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'),
- disableAnimatedMfm: defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v),
- useOsNativeEmojis: defaultStore.makeGetterSetter('useOsNativeEmojis'),
- disableShowingAnimatedImages: defaultStore.makeGetterSetter('disableShowingAnimatedImages'),
- loadRawImages: defaultStore.makeGetterSetter('loadRawImages'),
- imageNewTab: defaultStore.makeGetterSetter('imageNewTab'),
- nsfw: defaultStore.makeGetterSetter('nsfw'),
- disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'),
- showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'),
- defaultSideView: defaultStore.makeGetterSetter('defaultSideView'),
- chatOpenBehavior: ColdDeviceStorage.makeGetterSetter('chatOpenBehavior'),
- instanceTicker: defaultStore.makeGetterSetter('instanceTicker'),
- enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'),
- useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'),
- squareAvatars: defaultStore.makeGetterSetter('squareAvatars'),
- aiChanMode: defaultStore.makeGetterSetter('aiChanMode'),
- },
-
- watch: {
- lang() {
- localStorage.setItem('lang', this.lang);
- localStorage.removeItem('locale');
- this.reloadAsk();
- },
-
- fontSize() {
- if (this.fontSize == null) {
- localStorage.removeItem('fontSize');
- } else {
- localStorage.setItem('fontSize', this.fontSize);
- }
- this.reloadAsk();
- },
-
- useSystemFont() {
- if (this.useSystemFont) {
- localStorage.setItem('useSystemFont', 't');
- } else {
- localStorage.removeItem('useSystemFont');
- }
- this.reloadAsk();
- },
-
- enableInfiniteScroll() {
- this.reloadAsk();
- },
-
- squareAvatars() {
- this.reloadAsk();
- },
-
- aiChanMode() {
- this.reloadAsk();
- },
-
- showGapBetweenNotesInTimeline() {
- this.reloadAsk();
- },
-
- instanceTicker() {
- this.reloadAsk();
- },
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async reloadAsk() {
- const { canceled } = await os.dialog({
- type: 'info',
- text: this.$ts.reloadToApplySetting,
- showCancelButton: true
- });
- if (canceled) return;
-
- unisonReload();
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/import-export.vue b/src/client/pages/settings/import-export.vue
deleted file mode 100644
index eeaa1f1602..0000000000
--- a/src/client/pages/settings/import-export.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<template>
-<div style="margin: 16px;">
- <FormSection>
- <template #label>{{ $ts._exportOrImport.allNotes }}</template>
- <MkButton :class="$style.button" inline @click="doExport('notes')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
- </FormSection>
- <FormSection>
- <template #label>{{ $ts._exportOrImport.followingList }}</template>
- <MkButton :class="$style.button" inline @click="doExport('following')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
- <MkButton :class="$style.button" inline @click="doImport('following', $event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton>
- </FormSection>
- <FormSection>
- <template #label>{{ $ts._exportOrImport.userLists }}</template>
- <MkButton :class="$style.button" inline @click="doExport('user-lists')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
- <MkButton :class="$style.button" inline @click="doImport('user-lists', $event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton>
- </FormSection>
- <FormSection>
- <template #label>{{ $ts._exportOrImport.muteList }}</template>
- <MkButton :class="$style.button" inline @click="doExport('muting')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
- <MkButton :class="$style.button" inline @click="doImport('muting', $event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton>
- </FormSection>
- <FormSection>
- <template #label>{{ $ts._exportOrImport.blockingList }}</template>
- <MkButton :class="$style.button" inline @click="doExport('blocking')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
- <MkButton :class="$style.button" inline @click="doImport('blocking', $event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton>
- </FormSection>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import FormSection from '@client/components/form/section.vue';
-import * as os from '@client/os';
-import { selectFile } from '@client/scripts/select-file';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormSection,
- MkButton,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.importAndExport,
- icon: 'fas fa-boxes',
- bg: 'var(--bg)',
- },
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- doExport(target) {
- os.api(
- target === 'notes' ? 'i/export-notes' :
- target === 'following' ? 'i/export-following' :
- target === 'blocking' ? 'i/export-blocking' :
- target === 'user-lists' ? 'i/export-user-lists' :
- target === 'muting' ? 'i/export-mute' :
- null, {})
- .then(() => {
- os.dialog({
- type: 'info',
- text: this.$ts.exportRequested
- });
- }).catch((e: any) => {
- os.dialog({
- type: 'error',
- text: e.message
- });
- });
- },
-
- async doImport(target, e) {
- const file = await selectFile(e.currentTarget || e.target);
-
- os.api(
- target === 'following' ? 'i/import-following' :
- target === 'user-lists' ? 'i/import-user-lists' :
- target === 'muting' ? 'i/import-muting' :
- target === 'blocking' ? 'i/import-blocking' :
- null, {
- fileId: file.id
- }).then(() => {
- os.dialog({
- type: 'info',
- text: this.$ts.importRequested
- });
- }).catch((e: any) => {
- os.dialog({
- type: 'error',
- text: e.message
- });
- });
- },
- }
-});
-</script>
-
-<style module>
-.button {
- margin-right: 16px;
-}
-</style>
diff --git a/src/client/pages/settings/index.vue b/src/client/pages/settings/index.vue
deleted file mode 100644
index cf053dbe63..0000000000
--- a/src/client/pages/settings/index.vue
+++ /dev/null
@@ -1,326 +0,0 @@
-<template>
-<div class="vvcocwet" :class="{ wide: !narrow }" ref="el">
- <div class="nav" v-if="!narrow || page == null">
- <MkSpacer :content-max="700">
- <div class="baaadecd">
- <div class="title">{{ $ts.settings }}</div>
- <MkInfo v-if="emailNotConfigured" warn class="info">{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></MkInfo>
- <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu>
- </div>
- </MkSpacer>
- </div>
- <div class="main">
- <component :is="component" :key="page" v-bind="pageProps"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue';
-import { i18n } from '@client/i18n';
-import MkInfo from '@client/components/ui/info.vue';
-import MkSuperMenu from '@client/components/ui/super-menu.vue';
-import { scroll } from '@client/scripts/scroll';
-import { signout } from '@client/account';
-import { unisonReload } from '@client/scripts/unison-reload';
-import * as symbols from '@client/symbols';
-import { instance } from '@client/instance';
-import { $i } from '@client/account';
-
-export default defineComponent({
- components: {
- MkInfo,
- MkSuperMenu,
- },
-
- props: {
- initialPage: {
- type: String,
- required: false
- }
- },
-
- setup(props, context) {
- const indexInfo = {
- title: i18n.locale.settings,
- icon: 'fas fa-cog',
- bg: 'var(--bg)',
- hideHeader: true,
- };
- const INFO = ref(indexInfo);
- const page = ref(props.initialPage);
- const narrow = ref(false);
- const view = ref(null);
- const el = ref(null);
- const menuDef = computed(() => [{
- title: i18n.locale.basicSettings,
- items: [{
- icon: 'fas fa-user',
- text: i18n.locale.profile,
- to: '/settings/profile',
- active: page.value === 'profile',
- }, {
- icon: 'fas fa-lock-open',
- text: i18n.locale.privacy,
- to: '/settings/privacy',
- active: page.value === 'privacy',
- }, {
- icon: 'fas fa-laugh',
- text: i18n.locale.reaction,
- to: '/settings/reaction',
- active: page.value === 'reaction',
- }, {
- icon: 'fas fa-cloud',
- text: i18n.locale.drive,
- to: '/settings/drive',
- active: page.value === 'drive',
- }, {
- icon: 'fas fa-bell',
- text: i18n.locale.notifications,
- to: '/settings/notifications',
- active: page.value === 'notifications',
- }, {
- icon: 'fas fa-envelope',
- text: i18n.locale.email,
- to: '/settings/email',
- active: page.value === 'email',
- }, {
- icon: 'fas fa-share-alt',
- text: i18n.locale.integration,
- to: '/settings/integration',
- active: page.value === 'integration',
- }, {
- icon: 'fas fa-lock',
- text: i18n.locale.security,
- to: '/settings/security',
- active: page.value === 'security',
- }],
- }, {
- title: i18n.locale.clientSettings,
- items: [{
- icon: 'fas fa-cogs',
- text: i18n.locale.general,
- to: '/settings/general',
- active: page.value === 'general',
- }, {
- icon: 'fas fa-palette',
- text: i18n.locale.theme,
- to: '/settings/theme',
- active: page.value === 'theme',
- }, {
- icon: 'fas fa-list-ul',
- text: i18n.locale.menu,
- to: '/settings/menu',
- active: page.value === 'menu',
- }, {
- icon: 'fas fa-music',
- text: i18n.locale.sounds,
- to: '/settings/sounds',
- active: page.value === 'sounds',
- }, {
- icon: 'fas fa-plug',
- text: i18n.locale.plugins,
- to: '/settings/plugin',
- active: page.value === 'plugin',
- }],
- }, {
- title: i18n.locale.otherSettings,
- items: [{
- icon: 'fas fa-boxes',
- text: i18n.locale.importAndExport,
- to: '/settings/import-export',
- active: page.value === 'import-export',
- }, {
- icon: 'fas fa-ban',
- text: i18n.locale.muteAndBlock,
- to: '/settings/mute-block',
- active: page.value === 'mute-block',
- }, {
- icon: 'fas fa-comment-slash',
- text: i18n.locale.wordMute,
- to: '/settings/word-mute',
- active: page.value === 'word-mute',
- }, {
- icon: 'fas fa-key',
- text: 'API',
- to: '/settings/api',
- active: page.value === 'api',
- }, {
- icon: 'fas fa-ellipsis-h',
- text: i18n.locale.other,
- to: '/settings/other',
- active: page.value === 'other',
- }],
- }, {
- items: [{
- type: 'button',
- icon: 'fas fa-trash',
- text: i18n.locale.clearCache,
- action: () => {
- localStorage.removeItem('locale');
- localStorage.removeItem('theme');
- unisonReload();
- },
- }, {
- type: 'button',
- icon: 'fas fa-sign-in-alt fa-flip-horizontal',
- text: i18n.locale.logout,
- action: () => {
- signout();
- },
- danger: true,
- },],
- }]);
-
- const pageProps = ref({});
- const component = computed(() => {
- if (page.value == null) return null;
- switch (page.value) {
- case 'accounts': return defineAsyncComponent(() => import('./accounts.vue'));
- case 'profile': return defineAsyncComponent(() => import('./profile.vue'));
- case 'privacy': return defineAsyncComponent(() => import('./privacy.vue'));
- case 'reaction': return defineAsyncComponent(() => import('./reaction.vue'));
- case 'drive': return defineAsyncComponent(() => import('./drive.vue'));
- case 'notifications': return defineAsyncComponent(() => import('./notifications.vue'));
- case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue'));
- case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
- case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
- case 'security': return defineAsyncComponent(() => import('./security.vue'));
- case '2fa': return defineAsyncComponent(() => import('./2fa.vue'));
- case 'api': return defineAsyncComponent(() => import('./api.vue'));
- case 'apps': return defineAsyncComponent(() => import('./apps.vue'));
- case 'other': return defineAsyncComponent(() => import('./other.vue'));
- case 'general': return defineAsyncComponent(() => import('./general.vue'));
- case 'email': return defineAsyncComponent(() => import('./email.vue'));
- case 'email/address': return defineAsyncComponent(() => import('./email-address.vue'));
- case 'email/notification': return defineAsyncComponent(() => import('./email-notification.vue'));
- case 'theme': return defineAsyncComponent(() => import('./theme.vue'));
- case 'theme/install': return defineAsyncComponent(() => import('./theme.install.vue'));
- case 'theme/manage': return defineAsyncComponent(() => import('./theme.manage.vue'));
- case 'menu': return defineAsyncComponent(() => import('./menu.vue'));
- case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
- case 'custom-css': return defineAsyncComponent(() => import('./custom-css.vue'));
- 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'));
- }
- });
-
- 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 });
- });
- }, { immediate: true });
-
- watch(() => props.initialPage, () => {
- if (props.initialPage == null && !narrow.value) {
- page.value = 'profile';
- } else {
- page.value = props.initialPage;
- if (props.initialPage == null) {
- INFO.value = indexInfo;
- }
- }
- });
-
- onMounted(() => {
- narrow.value = el.value.offsetWidth < 800;
- if (!narrow.value) {
- page.value = 'profile';
- }
- });
-
- const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
-
- return {
- [symbols.PAGE_INFO]: INFO,
- page,
- menuDef,
- narrow,
- view,
- el,
- pageProps,
- component,
- emailNotConfigured,
- };
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.vvcocwet {
- > .nav {
- .baaadecd {
- > .title {
- margin: 16px;
- font-size: 1.5em;
- font-weight: bold;
- }
-
- > .info {
- margin: 0 16px;
- }
-
- > .accounts {
- > .avatar {
- display: block;
- width: 50px;
- height: 50px;
- margin: 8px auto 16px auto;
- }
- }
- }
- }
-
- &.wide {
- display: flex;
- max-width: 1000px;
- margin: 0 auto;
- height: 100%;
-
- > .nav {
- width: 32%;
- box-sizing: border-box;
- overflow: auto;
-
- .baaadecd {
- > .title {
- margin: 24px 0;
- }
- }
- }
-
- > .main {
- flex: 1;
- min-width: 0;
- overflow: auto;
- }
- }
-}
-</style>
diff --git a/src/client/pages/settings/integration.vue b/src/client/pages/settings/integration.vue
deleted file mode 100644
index 7f398dde9d..0000000000
--- a/src/client/pages/settings/integration.vue
+++ /dev/null
@@ -1,141 +0,0 @@
-<template>
-<FormBase>
- <div class="_debobigegoItem" v-if="enableTwitterIntegration">
- <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" @click="disconnectTwitter" danger>{{ $ts.disconnectService }}</MkButton>
- <MkButton v-else @click="connectTwitter" primary>{{ $ts.connectService }}</MkButton>
- </div>
- </div>
-
- <div class="_debobigegoItem" v-if="enableDiscordIntegration">
- <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" @click="disconnectDiscord" danger>{{ $ts.disconnectService }}</MkButton>
- <MkButton v-else @click="connectDiscord" primary>{{ $ts.connectService }}</MkButton>
- </div>
- </div>
-
- <div class="_debobigegoItem" v-if="enableGithubIntegration">
- <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" @click="disconnectGithub" danger>{{ $ts.disconnectService }}</MkButton>
- <MkButton v-else @click="connectGithub" primary>{{ $ts.connectService }}</MkButton>
- </div>
- </div>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { apiUrl } from '@client/config';
-import FormBase from '@client/components/debobigego/base.vue';
-import MkButton from '@client/components/ui/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- MkButton
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.integration,
- icon: 'fas fa-share-alt',
- bg: 'var(--bg)',
- },
- apiUrl,
- twitterForm: null,
- discordForm: null,
- githubForm: null,
- enableTwitterIntegration: false,
- enableDiscordIntegration: false,
- enableGithubIntegration: false,
- };
- },
-
- computed: {
- integrations() {
- return this.$i.integrations;
- },
-
- meta() {
- return this.$instance;
- },
- },
-
- created() {
- this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
- this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
- this.enableGithubIntegration = this.meta.enableGithubIntegration;
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
- document.cookie = `igi=${this.$i.token}; path=/;` +
- ` max-age=31536000;` +
- (document.location.protocol.startsWith('https') ? ' secure' : '');
-
- this.$watch('integrations', () => {
- if (this.integrations.twitter) {
- if (this.twitterForm) this.twitterForm.close();
- }
- if (this.integrations.discord) {
- if (this.discordForm) this.discordForm.close();
- }
- if (this.integrations.github) {
- if (this.githubForm) this.githubForm.close();
- }
- }, {
- deep: true
- });
- },
-
- methods: {
- connectTwitter() {
- this.twitterForm = window.open(apiUrl + '/connect/twitter',
- 'twitter_connect_window',
- 'height=570, width=520');
- },
-
- disconnectTwitter() {
- window.open(apiUrl + '/disconnect/twitter',
- 'twitter_disconnect_window',
- 'height=570, width=520');
- },
-
- connectDiscord() {
- this.discordForm = window.open(apiUrl + '/connect/discord',
- 'discord_connect_window',
- 'height=570, width=520');
- },
-
- disconnectDiscord() {
- window.open(apiUrl + '/disconnect/discord',
- 'discord_disconnect_window',
- 'height=570, width=520');
- },
-
- connectGithub() {
- this.githubForm = window.open(apiUrl + '/connect/github',
- 'github_connect_window',
- 'height=570, width=520');
- },
-
- disconnectGithub() {
- window.open(apiUrl + '/disconnect/github',
- 'github_disconnect_window',
- 'height=570, width=520');
- },
- }
-});
-</script>
diff --git a/src/client/pages/settings/menu.vue b/src/client/pages/settings/menu.vue
deleted file mode 100644
index 31472eb0c1..0000000000
--- a/src/client/pages/settings/menu.vue
+++ /dev/null
@@ -1,117 +0,0 @@
-<template>
-<FormBase>
- <FormTextarea v-model="items" tall manual-save>
- <span>{{ $ts.menu }}</span>
- <template #desc><button class="_textButton" @click="addItem">{{ $ts.addItem }}</button></template>
- </FormTextarea>
-
- <FormRadios v-model="menuDisplay">
- <template #desc>{{ $ts.display }}</template>
- <option value="sideFull">{{ $ts._menuDisplay.sideFull }}</option>
- <option value="sideIcon">{{ $ts._menuDisplay.sideIcon }}</option>
- <option value="top">{{ $ts._menuDisplay.top }}</option>
- <!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ $ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: ใ‚ตใ‚คใƒ‰ใƒใƒผใ‚’ๅฎŒๅ…จใซ้š ใ›ใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹ใจใ€ๅˆฅ้€”ใƒใƒณใƒใƒผใ‚ฌใƒผใƒœใ‚ฟใƒณใฎใ‚ˆใ†ใชใ‚‚ใฎใ‚’UIใซ่กจ็คบใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Š้ขๅ€’ -->
- </FormRadios>
-
- <FormButton @click="reset()" danger><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormRadios from '@client/components/debobigego/radios.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import { menuDef } from '@client/menu';
-import { defaultStore } from '@client/store';
-import * as symbols from '@client/symbols';
-import { unisonReload } from '@client/scripts/unison-reload';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- FormTextarea,
- FormRadios,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.menu,
- icon: 'fas fa-list-ul',
- bg: 'var(--bg)',
- },
- menuDef: menuDef,
- items: defaultStore.state.menu.join('\n'),
- }
- },
-
- computed: {
- splited(): string[] {
- return this.items.trim().split('\n').filter(x => x.trim() !== '');
- },
-
- menuDisplay: defaultStore.makeGetterSetter('menuDisplay')
- },
-
- watch: {
- menuDisplay() {
- this.reloadAsk();
- },
-
- items() {
- this.save();
- },
- },
-
- 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));
- const { canceled, result: item } = await os.dialog({
- type: null,
- title: this.$ts.addItem,
- select: {
- items: [...menu.map(k => ({
- value: k, text: this.$ts[this.menuDef[k].title]
- })), ...[{
- value: '-', text: this.$ts.divider
- }]]
- },
- showCancelButton: true
- });
- if (canceled) return;
- this.items = [...this.splited, item].join('\n');
- },
-
- save() {
- this.$store.set('menu', this.splited);
- this.reloadAsk();
- },
-
- reset() {
- this.$store.reset('menu');
- this.items = this.$store.state.menu.join('\n');
- },
-
- async reloadAsk() {
- const { canceled } = await os.dialog({
- type: 'info',
- text: this.$ts.reloadToApplySetting,
- showCancelButton: true
- });
- if (canceled) return;
-
- unisonReload();
- }
- },
-});
-</script>
diff --git a/src/client/pages/settings/mute-block.vue b/src/client/pages/settings/mute-block.vue
deleted file mode 100644
index 18b2fc0af4..0000000000
--- a/src/client/pages/settings/mute-block.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <MkTab v-model="tab" style="margin-bottom: var(--margin);">
- <option value="mute">{{ $ts.mutedUsers }}</option>
- <option value="block">{{ $ts.blockedUsers }}</option>
- </MkTab>
- <div v-if="tab === 'mute'">
- <MkPagination :pagination="mutingPagination" class="muting">
- <template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
- <template #default="{items}">
- <FormGroup>
- <FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)">
- <MkAcct :user="mute.mutee"/>
- </FormLink>
- </FormGroup>
- </template>
- </MkPagination>
- </div>
- <div v-if="tab === 'block'">
- <MkPagination :pagination="blockingPagination" class="blocking">
- <template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
- <template #default="{items}">
- <FormGroup>
- <FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)">
- <MkAcct :user="block.blockee"/>
- </FormLink>
- </FormGroup>
- </template>
- </MkPagination>
- </div>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkTab from '@client/components/tab.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import { userPage } from '@client/filters/user';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkTab,
- FormInfo,
- FormBase,
- FormGroup,
- FormLink,
- },
-
- emits: ['info'],
-
- 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,
- },
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- userPage
- }
-});
-</script>
diff --git a/src/client/pages/settings/notifications.vue b/src/client/pages/settings/notifications.vue
deleted file mode 100644
index 5f84349474..0000000000
--- a/src/client/pages/settings/notifications.vue
+++ /dev/null
@@ -1,77 +0,0 @@
-<template>
-<FormBase>
- <FormLink @click="configure">{{ $ts.notificationSetting }}</FormLink>
- <FormGroup>
- <FormButton @click="readAllNotifications">{{ $ts.markAsReadAllNotifications }}</FormButton>
- <FormButton @click="readAllUnreadNotes">{{ $ts.markAsReadAllUnreadNotes }}</FormButton>
- <FormButton @click="readAllMessagingMessages">{{ $ts.markAsReadAllTalkMessages }}</FormButton>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import { notificationTypes } from '@/types';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormLink,
- FormButton,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.notifications,
- icon: 'fas fa-bell',
- bg: 'var(--bg)',
- },
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- readAllUnreadNotes() {
- os.api('i/read-all-unread-notes');
- },
-
- readAllMessagingMessages() {
- os.api('i/read-all-messaging-messages');
- },
-
- readAllNotifications() {
- os.api('notifications/mark-all-as-read');
- },
-
- configure() {
- const includingTypes = notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x));
- os.popup(import('@client/components/notification-setting-window.vue'), {
- includingTypes,
- showGlobalToggle: false,
- }, {
- done: async (res) => {
- const { includingTypes: value } = res;
- await os.apiWithDialog('i/update', {
- mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)),
- }).then(i => {
- this.$i.mutingNotificationTypes = i.mutingNotificationTypes;
- });
- }
- }, 'closed');
- },
- }
-});
-</script>
diff --git a/src/client/pages/settings/other.vue b/src/client/pages/settings/other.vue
deleted file mode 100644
index 2eb922453f..0000000000
--- a/src/client/pages/settings/other.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<template>
-<FormBase>
- <FormLink to="/settings/update">Misskey Update</FormLink>
-
- <FormSwitch :value="$i.injectFeaturedNote" @update:modelValue="onChangeInjectFeaturedNote">
- {{ $ts.showFeaturedNotesInTimeline }}
- </FormSwitch>
-
- <FormSwitch v-model="reportError">{{ $ts.sendErrorReports }}<template #desc>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch>
-
- <FormLink to="/settings/account-info">{{ $ts.accountInfo }}</FormLink>
- <FormLink to="/settings/experimental-features">{{ $ts.experimentalFeatures }}</FormLink>
-
- <FormGroup>
- <template #label>{{ $ts.developer }}</template>
- <FormSwitch v-model="debug" @update:modelValue="changeDebug">
- DEBUG MODE
- </FormSwitch>
- <template v-if="debug">
- <FormButton @click="taskmanager">Task Manager</FormButton>
- </template>
- </FormGroup>
-
- <FormLink to="/settings/registry"><template #icon><i class="fas fa-cogs"></i></template>{{ $ts.registry }}</FormLink>
-
- <FormLink to="/bios" behavior="browser"><template #icon><i class="fas fa-door-open"></i></template>BIOS</FormLink>
- <FormLink to="/cli" behavior="browser"><template #icon><i class="fas fa-door-open"></i></template>CLI</FormLink>
-
- <FormLink to="/settings/delete-account"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import { debug } from '@client/config';
-import { defaultStore } from '@client/store';
-import { unisonReload } from '@client/scripts/unison-reload';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSelect,
- FormSwitch,
- FormButton,
- FormLink,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.other,
- icon: 'fas fa-ellipsis-h',
- bg: 'var(--bg)',
- },
- debug,
- }
- },
-
- computed: {
- reportError: defaultStore.makeGetterSetter('reportError'),
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- changeDebug(v) {
- console.log(v);
- localStorage.setItem('debug', v.toString());
- unisonReload();
- },
-
- onChangeInjectFeaturedNote(v) {
- os.api('i/update', {
- injectFeaturedNote: v
- });
- },
-
- taskmanager() {
- os.popup(import('@client/components/taskmanager.vue'), {
- }, {}, 'closed');
- },
- }
-});
-</script>
diff --git a/src/client/pages/settings/plugin.install.vue b/src/client/pages/settings/plugin.install.vue
deleted file mode 100644
index 709ef11abb..0000000000
--- a/src/client/pages/settings/plugin.install.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<template>
-<FormBase>
- <FormInfo warn>{{ $ts._plugin.installWarn }}</FormInfo>
-
- <FormGroup>
- <FormTextarea v-model="code" tall>
- <span>{{ $ts.code }}</span>
- </FormTextarea>
- </FormGroup>
-
- <FormButton @click="install" :disabled="code == null" primary inline><i class="fas fa-check"></i> {{ $ts.install }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { AiScript, parse } from '@syuilo/aiscript';
-import { serialize } from '@syuilo/aiscript/built/serializer';
-import { v4 as uuid } from 'uuid';
-import FormTextarea from '@client/components/form/textarea.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormRadios from '@client/components/form/radios.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import { unisonReload } from '@client/scripts/unison-reload';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormTextarea,
- FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
- FormLink,
- FormButton,
- FormInfo,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts._plugin.install,
- icon: 'fas fa-download',
- bg: 'var(--bg)',
- },
- code: null,
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- installPlugin({ id, meta, ast, token }) {
- ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({
- ...meta,
- id,
- active: true,
- configData: {},
- token: token,
- ast: ast
- }));
- },
-
- async install() {
- let ast;
- try {
- ast = parse(this.code);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: 'Syntax error :('
- });
- return;
- }
- const meta = AiScript.collectMetadata(ast);
- if (meta == null) {
- os.dialog({
- type: 'error',
- text: 'No metadata found :('
- });
- return;
- }
- const data = meta.get(null);
- if (data == null) {
- os.dialog({
- type: 'error',
- text: 'No metadata found :('
- });
- return;
- }
- const { name, version, author, description, permissions, config } = data;
- if (name == null || version == null || author == null) {
- os.dialog({
- type: 'error',
- text: 'Required property not found :('
- });
- return;
- }
-
- const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => {
- os.popup(import('@client/components/token-generate-window.vue'), {
- title: this.$ts.tokenRequested,
- information: this.$ts.pluginTokenRequestedDescription,
- initialName: name,
- initialPermissions: permissions
- }, {
- done: async result => {
- const { name, permissions } = result;
- const { token } = await os.api('miauth/gen-token', {
- session: null,
- name: name,
- permission: permissions,
- });
-
- res(token);
- }
- }, 'closed');
- });
-
- this.installPlugin({
- id: uuid(),
- meta: {
- name, version, author, description, permissions, config
- },
- token,
- ast: serialize(ast)
- });
-
- os.success();
-
- this.$nextTick(() => {
- unisonReload();
- });
- },
- }
-});
-</script>
diff --git a/src/client/pages/settings/plugin.manage.vue b/src/client/pages/settings/plugin.manage.vue
deleted file mode 100644
index f1c27f1e3c..0000000000
--- a/src/client/pages/settings/plugin.manage.vue
+++ /dev/null
@@ -1,115 +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 :value="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 @click="config(plugin)" inline v-if="plugin.config"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton>
- <MkButton @click="uninstall(plugin)" inline danger><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 '@client/components/ui/button.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkSelect from '@client/components/form/select.vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import * as symbols from '@client/symbols';
-
-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/src/client/pages/settings/plugin.vue b/src/client/pages/settings/plugin.vue
deleted file mode 100644
index 23f263bbbd..0000000000
--- a/src/client/pages/settings/plugin.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<template>
-<FormBase>
- <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>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormLink,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.plugins,
- icon: 'fas fa-plug',
- bg: 'var(--bg)',
- },
- plugins: ColdDeviceStorage.get('plugins').length,
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/settings/privacy.vue b/src/client/pages/settings/privacy.vue
deleted file mode 100644
index 2a60ae1f46..0000000000
--- a/src/client/pages/settings/privacy.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <FormSwitch v-model="isLocked" @update:modelValue="save()">{{ $ts.makeFollowManuallyApprove }}</FormSwitch>
- <FormSwitch v-model="autoAcceptFollowed" :disabled="!isLocked" @update:modelValue="save()">{{ $ts.autoAcceptFollowed }}</FormSwitch>
- <template #caption>{{ $ts.lockedAccountInfo }}</template>
- </FormGroup>
- <FormSwitch v-model="publicReactions" @update:modelValue="save()">
- {{ $ts.makeReactionsPublic }}
- <template #desc>{{ $ts.makeReactionsPublicDescription }}</template>
- </FormSwitch>
- <FormSwitch v-model="hideOnlineStatus" @update:modelValue="save()">
- {{ $ts.hideOnlineStatus }}
- <template #desc>{{ $ts.hideOnlineStatusDescription }}</template>
- </FormSwitch>
- <FormSwitch v-model="noCrawle" @update:modelValue="save()">
- {{ $ts.noCrawle }}
- <template #desc>{{ $ts.noCrawleDescription }}</template>
- </FormSwitch>
- <FormSwitch v-model="isExplorable" @update:modelValue="save()">
- {{ $ts.makeExplorable }}
- <template #desc>{{ $ts.makeExplorableDescription }}</template>
- </FormSwitch>
- <FormSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ $ts.rememberNoteVisibility }}</FormSwitch>
- <FormGroup v-if="!rememberNoteVisibility">
- <template #label>{{ $ts.defaultNoteVisibility }}</template>
- <FormSelect v-model="defaultNoteVisibility">
- <option value="public">{{ $ts._visibility.public }}</option>
- <option value="home">{{ $ts._visibility.home }}</option>
- <option value="followers">{{ $ts._visibility.followers }}</option>
- <option value="specified">{{ $ts._visibility.specified }}</option>
- </FormSelect>
- <FormSwitch v-model="defaultNoteLocalOnly">{{ $ts._visibility.localOnly }}</FormSwitch>
- </FormGroup>
- <FormSwitch v-model="keepCw" @update:modelValue="save()">{{ $ts.keepCw }}</FormSwitch>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormSelect from '@client/components/debobigego/select.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import * as os from '@client/os';
-import { defaultStore } from '@client/store';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormSelect,
- 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,
- }
- },
-
- computed: {
- defaultNoteVisibility: defaultStore.makeGetterSetter('defaultNoteVisibility'),
- defaultNoteLocalOnly: defaultStore.makeGetterSetter('defaultNoteLocalOnly'),
- rememberNoteVisibility: defaultStore.makeGetterSetter('rememberNoteVisibility'),
- keepCw: 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;
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- save() {
- os.api('i/update', {
- isLocked: !!this.isLocked,
- autoAcceptFollowed: !!this.autoAcceptFollowed,
- noCrawle: !!this.noCrawle,
- isExplorable: !!this.isExplorable,
- hideOnlineStatus: !!this.hideOnlineStatus,
- publicReactions: !!this.publicReactions,
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/profile.vue b/src/client/pages/settings/profile.vue
deleted file mode 100644
index b993b5fc72..0000000000
--- a/src/client/pages/settings/profile.vue
+++ /dev/null
@@ -1,281 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <div class="_debobigegoItem _debobigegoPanel llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
- <MkAvatar class="avatar" :user="$i"/>
- </div>
- <FormButton @click="changeAvatar" primary>{{ $ts._profile.changeAvatar }}</FormButton>
- <FormButton @click="changeBanner" primary>{{ $ts._profile.changeBanner }}</FormButton>
- </FormGroup>
-
- <FormInput v-model="name" :max="30" manual-save>
- <span>{{ $ts._profile.name }}</span>
- </FormInput>
-
- <FormTextarea v-model="description" :max="500" tall manual-save>
- <span>{{ $ts._profile.description }}</span>
- <template #desc>{{ $ts._profile.youCanIncludeHashtags }}</template>
- </FormTextarea>
-
- <FormInput v-model="location" manual-save>
- <span>{{ $ts.location }}</span>
- <template #prefix><i class="fas fa-map-marker-alt"></i></template>
- </FormInput>
-
- <FormInput v-model="birthday" type="date" manual-save>
- <span>{{ $ts.birthday }}</span>
- <template #prefix><i class="fas fa-birthday-cake"></i></template>
- </FormInput>
-
- <FormSelect v-model="lang">
- <template #label>{{ $ts.language }}</template>
- <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
- </FormSelect>
-
- <FormGroup>
- <FormButton @click="editMetadata" primary>{{ $ts._profile.metadataEdit }}</FormButton>
- <template #caption>{{ $ts._profile.metadataDescription }}</template>
- </FormGroup>
-
- <FormSwitch v-model="isCat">{{ $ts.flagAsCat }}<template #desc>{{ $ts.flagAsCatDescription }}</template></FormSwitch>
-
- <FormSwitch v-model="isBot">{{ $ts.flagAsBot }}<template #desc>{{ $ts.flagAsBotDescription }}</template></FormSwitch>
-
- <FormSwitch v-model="alwaysMarkNsfw">{{ $ts.alwaysMarkSensitive }}</FormSwitch>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormSelect from '@client/components/debobigego/select.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import { host, langs } from '@client/config';
-import { selectFile } from '@client/scripts/select-file';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormButton,
- FormInput,
- FormTextarea,
- FormSwitch,
- FormSelect,
- FormBase,
- FormGroup,
- },
-
- emits: ['info'],
-
- 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,
- }
- },
-
- 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;
-
- 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;
-
- 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);
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- changeAvatar(e) {
- selectFile(e.currentTarget || e.target, this.$ts.avatar).then(file => {
- os.api('i/update', {
- avatarId: file.id,
- });
- });
- },
-
- changeBanner(e) {
- selectFile(e.currentTarget || e.target, this.$ts.banner).then(file => {
- os.api('i/update', {
- bannerId: file.id,
- });
- });
- },
-
- 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 },
- ];
-
- os.api('i/update', {
- fields,
- }).then(i => {
- os.success();
- }).catch(err => {
- os.dialog({
- type: 'error',
- text: err.id
- });
- });
- },
-
- save() {
- this.saving = true;
-
- 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;
- });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.llvierxe {
- position: relative;
- height: 150px;
- background-size: cover;
- background-position: center;
-
- > * {
- pointer-events: none;
- }
-
- > .avatar {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- display: block;
- width: 72px;
- height: 72px;
- margin: auto;
- box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.5);
- }
-}
-</style>
diff --git a/src/client/pages/settings/reaction.vue b/src/client/pages/settings/reaction.vue
deleted file mode 100644
index a5ff46097d..0000000000
--- a/src/client/pages/settings/reaction.vue
+++ /dev/null
@@ -1,152 +0,0 @@
-<template>
-<FormBase>
- <div class="_debobigegoItem">
- <div class="_debobigegoLabel">{{ $ts.reactionSettingDescription }}</div>
- <div class="_debobigegoPanel">
- <XDraggable class="zoaiodol" v-model="reactions" :item-key="item => item" animation="150" delay="100" delay-on-touch-only="true">
- <template #item="{element}">
- <button class="_button item" @click="remove(element, $event)">
- <MkEmoji :emoji="element" :normal="true"/>
- </button>
- </template>
- <template #footer>
- <button class="_button add" @click="chooseEmoji"><i class="fas fa-plus"></i></button>
- </template>
- </XDraggable>
- </div>
- <div class="_debobigegoCaption">{{ $ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ $ts.preview }}</button></div>
- </div>
-
- <FormRadios v-model="reactionPickerWidth">
- <template #desc>{{ $ts.width }}</template>
- <option :value="1">{{ $ts.small }}</option>
- <option :value="2">{{ $ts.medium }}</option>
- <option :value="3">{{ $ts.large }}</option>
- </FormRadios>
- <FormRadios v-model="reactionPickerHeight">
- <template #desc>{{ $ts.height }}</template>
- <option :value="1">{{ $ts.small }}</option>
- <option :value="2">{{ $ts.medium }}</option>
- <option :value="3">{{ $ts.large }}</option>
- </FormRadios>
- <FormButton @click="preview"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton>
- <FormButton danger @click="setDefault"><i class="fas fa-undo"></i> {{ $ts.default }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XDraggable from 'vuedraggable';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormRadios from '@client/components/debobigego/radios.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import { defaultStore } from '@client/store';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormInput,
- FormButton,
- FormBase,
- FormRadios,
- XDraggable,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.reaction,
- icon: 'fas fa-laugh',
- action: {
- icon: 'fas fa-eye',
- handler: this.preview
- },
- bg: 'var(--bg)',
- },
- reactions: JSON.parse(JSON.stringify(this.$store.state.reactions)),
- }
- },
-
- computed: {
- reactionPickerWidth: defaultStore.makeGetterSetter('reactionPickerWidth'),
- reactionPickerHeight: defaultStore.makeGetterSetter('reactionPickerHeight'),
- },
-
- watch: {
- reactions: {
- handler() {
- this.save();
- },
- deep: true
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- save() {
- this.$store.set('reactions', this.reactions);
- },
-
- remove(reaction, ev) {
- os.popupMenu([{
- text: this.$ts.remove,
- action: () => {
- this.reactions = this.reactions.filter(x => x !== reaction)
- }
- }], ev.currentTarget || ev.target);
- },
-
- preview(ev) {
- os.popup(import('@client/components/emoji-picker-dialog.vue'), {
- asReactionPicker: true,
- src: ev.currentTarget || ev.target,
- }, {}, 'closed');
- },
-
- async setDefault() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$ts.resetAreYouSure,
- showCancelButton: true
- });
- if (canceled) return;
-
- this.reactions = JSON.parse(JSON.stringify(this.$store.def.reactions.default));
- },
-
- chooseEmoji(ev) {
- os.pickEmoji(ev.currentTarget || ev.target, {
- showPinned: false
- }).then(emoji => {
- if (!this.reactions.includes(emoji)) {
- this.reactions.push(emoji);
- }
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.zoaiodol {
- padding: 16px;
-
- > .item {
- display: inline-block;
- padding: 8px;
- cursor: move;
- }
-
- > .add {
- display: inline-block;
- padding: 8px;
- }
-}
-</style>
diff --git a/src/client/pages/settings/registry.keys.vue b/src/client/pages/settings/registry.keys.vue
deleted file mode 100644
index d99002e50f..0000000000
--- a/src/client/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 @click="createKey" primary>{{ $ts._registry.createKey }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/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/src/client/pages/settings/registry.value.vue b/src/client/pages/settings/registry.value.vue
deleted file mode 100644
index 06be5737e9..0000000000
--- a/src/client/pages/settings/registry.value.vue
+++ /dev/null
@@ -1,149 +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 tall v-model="valueForEditor" class="_monospace" style="tab-size: 2;">
- <span>{{ $ts.value }} (JSON)</span>
- </FormTextarea>
- <FormButton @click="save" primary><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 '@client/components/debobigego/info.vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormTextarea from '@client/components/form/textarea.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/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.dialog({
- type: 'error',
- text: this.$ts.invalidValue
- });
- return;
- }
-
- os.dialog({
- type: 'warning',
- text: this.$ts.saveConfirm,
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
- os.apiWithDialog('i/registry/set', {
- scope: this.scope,
- key: this.xKey,
- value: JSON5.parse(this.valueForEditor)
- });
- });
- },
-
- del() {
- os.dialog({
- type: 'warning',
- text: this.$ts.deleteConfirm,
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
- os.apiWithDialog('i/registry/remove', {
- scope: this.scope,
- key: this.xKey
- });
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/registry.vue b/src/client/pages/settings/registry.vue
deleted file mode 100644
index e4fb230d5c..0000000000
--- a/src/client/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 @click="createKey" primary>{{ $ts._registry.createKey }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/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/src/client/pages/settings/security.vue b/src/client/pages/settings/security.vue
deleted file mode 100644
index e051685a82..0000000000
--- a/src/client/pages/settings/security.vue
+++ /dev/null
@@ -1,158 +0,0 @@
-<template>
-<FormBase>
- <X2fa/>
- <FormLink to="/settings/2fa"><template #icon><i class="fas fa-mobile-alt"></i></template>{{ $ts.twoStepAuthentication }}</FormLink>
- <FormButton primary @click="change()">{{ $ts.changePassword }}</FormButton>
- <FormPagination :pagination="pagination">
- <template #label>{{ $ts.signinHistory }}</template>
- <template #default="{items}">
- <div class="_debobigegoPanel timnmucd" v-for="item in items" :key="item.id">
- <header>
- <i v-if="item.success" class="fas fa-check icon succ"></i>
- <i v-else class="fas fa-times-circle icon fail"></i>
- <code class="ip _monospace">{{ item.ip }}</code>
- <MkTime :time="item.createdAt" class="time"/>
- </header>
- </div>
- </template>
- </FormPagination>
- <FormGroup>
- <FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ $ts.regenerateLoginToken }}</FormButton>
- <template #caption>{{ $ts.regenerateLoginTokenDescription }}</template>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormPagination from '@client/components/debobigego/pagination.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormLink,
- FormButton,
- FormPagination,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.security,
- icon: 'fas fa-lock',
- bg: 'var(--bg)',
- },
- pagination: {
- endpoint: 'i/signin-history',
- limit: 5,
- },
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async change() {
- const { canceled: canceled1, result: currentPassword } = await os.dialog({
- title: this.$ts.currentPassword,
- input: {
- type: 'password'
- }
- });
- if (canceled1) return;
-
- const { canceled: canceled2, result: newPassword } = await os.dialog({
- title: this.$ts.newPassword,
- input: {
- type: 'password'
- }
- });
- if (canceled2) return;
-
- const { canceled: canceled3, result: newPassword2 } = await os.dialog({
- title: this.$ts.newPasswordRetype,
- input: {
- type: 'password'
- }
- });
- if (canceled3) return;
-
- if (newPassword !== newPassword2) {
- os.dialog({
- type: 'error',
- text: this.$ts.retypedNotMatch
- });
- return;
- }
-
- os.apiWithDialog('i/change-password', {
- currentPassword,
- newPassword
- });
- },
-
- regenerateToken() {
- os.dialog({
- title: this.$ts.password,
- input: {
- type: 'password'
- }
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- os.api('i/regenerate_token', {
- password: password
- });
- });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.timnmucd {
- padding: 16px;
-
- > header {
- display: flex;
- align-items: center;
-
- > .icon {
- width: 1em;
- margin-right: 0.75em;
-
- &.succ {
- color: var(--success);
- }
-
- &.fail {
- color: var(--error);
- }
- }
-
- > .ip {
- flex: 1;
- min-width: 0;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-right: 12px;
- }
-
- > .time {
- margin-left: auto;
- opacity: 0.7;
- }
- }
-}
-</style>
diff --git a/src/client/pages/settings/sounds.vue b/src/client/pages/settings/sounds.vue
deleted file mode 100644
index 07310619c8..0000000000
--- a/src/client/pages/settings/sounds.vue
+++ /dev/null
@@ -1,155 +0,0 @@
-<template>
-<FormBase>
- <FormRange v-model="masterVolume" :min="0" :max="1" :step="0.05">
- <template #label><i class="fas fa-volume-icon"></i> {{ $ts.masterVolume }}</template>
- </FormRange>
-
- <FormGroup>
- <template #label>{{ $ts.sounds }}</template>
- <FormButton v-for="type in Object.keys(sounds)" :key="type" :center="false" @click="edit(type)">
- {{ $t('_sfx.' + type) }}
- <template #suffix>{{ sounds[type].type || $ts.none }}</template>
- <template #suffixIcon><i class="fas fa-chevron-down"></i></template>
- </FormButton>
- </FormGroup>
-
- <FormButton @click="reset()" danger><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormRange from '@client/components/debobigego/range.vue';
-import FormSelect from '@client/components/debobigego/select.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import { playFile } from '@client/scripts/sound';
-import * as symbols from '@client/symbols';
-
-const soundsTypes = [
- null,
- 'syuilo/up',
- 'syuilo/down',
- 'syuilo/pope1',
- 'syuilo/pope2',
- 'syuilo/waon',
- 'syuilo/popo',
- 'syuilo/triple',
- 'syuilo/poi1',
- 'syuilo/poi2',
- 'syuilo/pirori',
- 'syuilo/pirori-wet',
- 'syuilo/pirori-square-wet',
- 'syuilo/square-pico',
- 'syuilo/reverved',
- 'syuilo/ryukyu',
- 'syuilo/kick',
- 'syuilo/snare',
- 'syuilo/queue-jammed',
- 'aisha/1',
- 'aisha/2',
- 'aisha/3',
- 'noizenecio/kick_gaba',
- 'noizenecio/kick_gaba2',
-];
-
-export default defineComponent({
- components: {
- FormSelect,
- FormButton,
- FormBase,
- FormRange,
- FormGroup,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.sounds,
- icon: 'fas fa-music',
- bg: 'var(--bg)',
- },
- sounds: {},
- }
- },
-
- computed: {
- masterVolume: { // TODO: (ๅค–้ƒจ)้–ขๆ•ฐใซcomputedใ‚’ไฝฟใ†ใฎใฏใ‚ขใƒฌใชใฎใง็›ดใ™
- get() { return ColdDeviceStorage.get('sound_masterVolume'); },
- set(value) { ColdDeviceStorage.set('sound_masterVolume', value); }
- },
- volumeIcon() {
- return this.masterVolume === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up';
- }
- },
-
- created() {
- this.sounds.note = ColdDeviceStorage.get('sound_note');
- this.sounds.noteMy = ColdDeviceStorage.get('sound_noteMy');
- this.sounds.notification = ColdDeviceStorage.get('sound_notification');
- this.sounds.chat = ColdDeviceStorage.get('sound_chat');
- 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: {
- async edit(type) {
- const { canceled, result } = await os.form(this.$t('_sfx.' + type), {
- type: {
- type: 'enum',
- enum: soundsTypes.map(x => ({
- value: x,
- label: x == null ? this.$ts.none : x,
- })),
- label: this.$ts.sound,
- default: this.sounds[type].type,
- },
- volume: {
- type: 'range',
- mim: 0,
- max: 1,
- step: 0.05,
- label: this.$ts.volume,
- default: this.sounds[type].volume
- },
- listen: {
- type: 'button',
- content: this.$ts.listen,
- action: (_, values) => {
- playFile(values.type, values.volume);
- }
- }
- });
- if (canceled) return;
-
- const v = {
- type: result.type,
- volume: result.volume,
- };
-
- ColdDeviceStorage.set('sound_' + type, v);
- this.sounds[type] = v;
- },
-
- reset() {
- for (const sound of Object.keys(this.sounds)) {
- const v = ColdDeviceStorage.default['sound_' + sound];
- ColdDeviceStorage.set('sound_' + sound, v);
- this.sounds[sound] = v;
- }
- }
- }
-});
-</script>
diff --git a/src/client/pages/settings/theme.install.vue b/src/client/pages/settings/theme.install.vue
deleted file mode 100644
index 9fbb28929d..0000000000
--- a/src/client/pages/settings/theme.install.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <FormTextarea v-model="installThemeCode">
- <span>{{ $ts._theme.code }}</span>
- </FormTextarea>
- <FormButton @click="() => preview(installThemeCode)" :disabled="installThemeCode == null" inline><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton>
- </FormGroup>
-
- <FormButton @click="() => install(installThemeCode)" :disabled="installThemeCode == null" primary inline><i class="fas fa-check"></i> {{ $ts.install }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import FormTextarea from '@client/components/form/textarea.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormRadios from '@client/components/form/radios.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import { applyTheme, validateTheme } from '@client/scripts/theme';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import { addTheme, getThemes } from '@client/theme-store';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormTextarea,
- FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
- FormLink,
- FormButton,
- },
-
- emits: ['info'],
-
- 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]);
- },
-
- methods: {
- parseThemeCode(code) {
- let theme;
-
- try {
- theme = JSON5.parse(code);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: this.$ts._theme.invalid
- });
- return false;
- }
- if (!validateTheme(theme)) {
- os.dialog({
- type: 'error',
- text: this.$ts._theme.invalid
- });
- return false;
- }
- if (getThemes().some(t => t.id === theme.id)) {
- os.dialog({
- type: 'info',
- text: this.$ts._theme.alreadyInstalled
- });
- return false;
- }
-
- return theme;
- },
-
- preview(code) {
- const theme = this.parseThemeCode(code);
- if (theme) applyTheme(theme, false);
- },
-
- async install(code) {
- const theme = this.parseThemeCode(code);
- if (!theme) return;
- await addTheme(theme);
- os.dialog({
- type: 'success',
- text: this.$t('_theme.installed', { name: theme.name })
- });
- },
- }
-});
-</script>
diff --git a/src/client/pages/settings/theme.manage.vue b/src/client/pages/settings/theme.manage.vue
deleted file mode 100644
index 1a11a664f0..0000000000
--- a/src/client/pages/settings/theme.manage.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-<template>
-<FormBase>
- <FormSelect v-model="selectedThemeId">
- <template #label>{{ $ts.theme }}</template>
- <optgroup :label="$ts._theme.installedThemes">
- <option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="$ts._theme.builtinThemes">
- <option v-for="x in builtinThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- </FormSelect>
- <template v-if="selectedTheme">
- <FormInput readonly :modelValue="selectedTheme.author">
- <span>{{ $ts.author }}</span>
- </FormInput>
- <FormTextarea readonly :modelValue="selectedTheme.desc" v-if="selectedTheme.desc">
- <span>{{ $ts._theme.description }}</span>
- </FormTextarea>
- <FormTextarea readonly tall :modelValue="selectedThemeCode">
- <span>{{ $ts._theme.code }}</span>
- <template #desc><button @click="copyThemeCode()" class="_textButton">{{ $ts.copy }}</button></template>
- </FormTextarea>
- <FormButton @click="uninstall()" danger v-if="!builtinThemes.some(t => t.id == selectedTheme.id)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</FormButton>
- </template>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as JSON5 from 'json5';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormSelect from '@client/components/debobigego/select.vue';
-import FormRadios from '@client/components/debobigego/radios.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import { Theme, builtinThemes } from '@client/scripts/theme';
-import copyToClipboard from '@client/scripts/copy-to-clipboard';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import { getThemes, removeTheme } from '@client/theme-store';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormTextarea,
- FormSelect,
- FormRadios,
- FormBase,
- FormGroup,
- FormInput,
- FormButton,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts._theme.manage,
- icon: 'fas fa-folder-open',
- bg: 'var(--bg)',
- },
- installedThemes: getThemes(),
- builtinThemes,
- selectedThemeId: null,
- }
- },
-
- computed: {
- themes(): Theme[] {
- return this.builtinThemes.concat(this.installedThemes);
- },
-
- selectedTheme() {
- if (this.selectedThemeId == null) return null;
- return this.themes.find(x => x.id === this.selectedThemeId);
- },
-
- selectedThemeCode() {
- if (this.selectedTheme == null) return null;
- return JSON5.stringify(this.selectedTheme, null, '\t');
- },
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- copyThemeCode() {
- copyToClipboard(this.selectedThemeCode);
- os.success();
- },
-
- uninstall() {
- removeTheme(this.selectedTheme);
- this.installedThemes = this.installedThemes.filter(t => t.id !== this.selectedThemeId);
- this.selectedThemeId = null;
- os.success();
- },
- }
-});
-</script>
diff --git a/src/client/pages/settings/theme.vue b/src/client/pages/settings/theme.vue
deleted file mode 100644
index c6be42251c..0000000000
--- a/src/client/pages/settings/theme.vue
+++ /dev/null
@@ -1,424 +0,0 @@
-<template>
-<FormBase>
- <FormGroup>
- <div class="rfqxtzch _debobigegoItem _debobigegoPanel">
- <div class="darkMode">
- <div class="toggleWrapper">
- <input type="checkbox" class="dn" id="dn" v-model="darkMode"/>
- <label for="dn" class="toggle">
- <span class="before">{{ $ts.light }}</span>
- <span class="after">{{ $ts.dark }}</span>
- <span class="toggle__handler">
- <span class="crater crater--1"></span>
- <span class="crater crater--2"></span>
- <span class="crater crater--3"></span>
- </span>
- <span class="star star--1"></span>
- <span class="star star--2"></span>
- <span class="star star--3"></span>
- <span class="star star--4"></span>
- <span class="star star--5"></span>
- <span class="star star--6"></span>
- </label>
- </div>
- </div>
- </div>
- <FormSwitch v-model="syncDeviceDarkMode">{{ $ts.syncDeviceDarkMode }}</FormSwitch>
- </FormGroup>
-
- <template v-if="darkMode">
- <FormSelect v-model="darkThemeId">
- <template #label>{{ $ts.themeForDarkMode }}</template>
- <optgroup :label="$ts.darkThemes">
- <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="$ts.lightThemes">
- <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- </FormSelect>
- <FormSelect v-model="lightThemeId">
- <template #label>{{ $ts.themeForLightMode }}</template>
- <optgroup :label="$ts.lightThemes">
- <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="$ts.darkThemes">
- <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- </FormSelect>
- </template>
- <template v-else>
- <FormSelect v-model="lightThemeId">
- <template #label>{{ $ts.themeForLightMode }}</template>
- <optgroup :label="$ts.lightThemes">
- <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="$ts.darkThemes">
- <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- </FormSelect>
- <FormSelect v-model="darkThemeId">
- <template #label>{{ $ts.themeForDarkMode }}</template>
- <optgroup :label="$ts.darkThemes">
- <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="$ts.lightThemes">
- <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
- </optgroup>
- </FormSelect>
- </template>
-
- <FormButton primary v-if="wallpaper == null" @click="setWallpaper">{{ $ts.setWallpaper }}</FormButton>
- <FormButton primary v-else @click="wallpaper = null">{{ $ts.removeWallpaper }}</FormButton>
-
- <FormGroup>
- <FormLink to="https://assets.misskey.io/theme/list" external><template #icon><i class="fas fa-globe"></i></template>{{ $ts._theme.explore }}</FormLink>
- <FormLink to="/settings/theme/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._theme.install }}</FormLink>
- </FormGroup>
-
- <FormGroup>
- <FormLink to="/theme-editor"><template #icon><i class="fas fa-paint-roller"></i></template>{{ $ts._theme.make }}</FormLink>
- <!--<FormLink to="/advanced-theme-editor"><template #icon><i class="fas fa-paint-roller"></i></template>{{ $ts._theme.make }} ({{ $ts.advanced }})</FormLink>-->
- </FormGroup>
-
- <FormLink to="/settings/theme/manage"><template #icon><i class="fas fa-folder-open"></i></template>{{ $ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, onActivated, onMounted, ref, watch } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormSelect from '@client/components/debobigego/select.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import { builtinThemes } from '@client/scripts/theme';
-import { selectFile } from '@client/scripts/select-file';
-import { isDeviceDarkmode } from '@client/scripts/is-device-darkmode';
-import { ColdDeviceStorage } from '@client/store';
-import { i18n } from '@client/i18n';
-import { defaultStore } from '@client/store';
-import { fetchThemes, getThemes } from '@client/theme-store';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormSelect,
- FormBase,
- FormGroup,
- FormLink,
- FormButton,
- },
-
- emits: ['info'],
-
- setup(props, { emit }) {
- const INFO = {
- title: i18n.locale.theme,
- icon: 'fas fa-palette',
- bg: 'var(--bg)',
- };
-
- const installedThemes = ref(getThemes());
- const themes = computed(() => builtinThemes.concat(installedThemes.value));
- const darkThemes = computed(() => themes.value.filter(t => t.base == 'dark' || t.kind == 'dark'));
- const lightThemes = computed(() => themes.value.filter(t => t.base == 'light' || t.kind == 'light'));
- const darkTheme = ColdDeviceStorage.ref('darkTheme');
- const darkThemeId = computed({
- get() {
- return darkTheme.value.id;
- },
- set(id) {
- ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id))
- }
- });
- const lightTheme = ColdDeviceStorage.ref('lightTheme');
- const lightThemeId = computed({
- get() {
- return lightTheme.value.id;
- },
- set(id) {
- ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id))
- }
- });
- const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
- const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
- const wallpaper = ref(localStorage.getItem('wallpaper'));
- const themesCount = installedThemes.value.length;
-
- watch(syncDeviceDarkMode, () => {
- if (syncDeviceDarkMode) {
- defaultStore.set('darkMode', isDeviceDarkmode());
- }
- });
-
- watch(wallpaper, () => {
- if (wallpaper.value == null) {
- localStorage.removeItem('wallpaper');
- } else {
- localStorage.setItem('wallpaper', wallpaper.value);
- }
- location.reload();
- });
-
- onMounted(() => {
- emit('info', INFO);
- });
-
- onActivated(() => {
- fetchThemes().then(() => {
- installedThemes.value = getThemes();
- });
- });
-
- fetchThemes().then(() => {
- installedThemes.value = getThemes();
- });
-
- return {
- [symbols.PAGE_INFO]: INFO,
- darkThemes,
- lightThemes,
- darkThemeId,
- lightThemeId,
- darkMode,
- syncDeviceDarkMode,
- themesCount,
- wallpaper,
- setWallpaper(e) {
- selectFile(e.currentTarget || e.target, null, false).then(file => {
- wallpaper.value = file.url;
- });
- },
- };
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.rfqxtzch {
- padding: 16px;
-
- > .darkMode {
- position: relative;
- padding: 32px 0;
-
- &.disabled {
- opacity: 0.7;
-
- &, * {
- cursor: not-allowed !important;
- }
- }
-
- .toggleWrapper {
- position: absolute;
- top: 50%;
- left: 50%;
- overflow: hidden;
- padding: 0 100px;
- transform: translate3d(-50%, -50%, 0);
-
- input {
- position: absolute;
- left: -99em;
- }
- }
-
- .toggle {
- cursor: pointer;
- display: inline-block;
- position: relative;
- width: 90px;
- height: 50px;
- background-color: #83D8FF;
- border-radius: 90px - 6;
- transition: background-color 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
-
- > .before, > .after {
- position: absolute;
- top: 15px;
- font-size: 18px;
- transition: color 1s ease;
- }
-
- > .before {
- left: -70px;
- color: var(--accent);
- }
-
- > .after {
- right: -68px;
- color: var(--fg);
- }
- }
-
- .toggle__handler {
- display: inline-block;
- position: relative;
- z-index: 1;
- top: 3px;
- left: 3px;
- width: 50px - 6;
- height: 50px - 6;
- background-color: #FFCF96;
- border-radius: 50px;
- box-shadow: 0 2px 6px rgba(0,0,0,.3);
- transition: all 400ms cubic-bezier(0.68, -0.55, 0.265, 1.55) !important;
- transform: rotate(-45deg);
-
- .crater {
- position: absolute;
- background-color: #E8CDA5;
- opacity: 0;
- transition: opacity 200ms ease-in-out !important;
- border-radius: 100%;
- }
-
- .crater--1 {
- top: 18px;
- left: 10px;
- width: 4px;
- height: 4px;
- }
-
- .crater--2 {
- top: 28px;
- left: 22px;
- width: 6px;
- height: 6px;
- }
-
- .crater--3 {
- top: 10px;
- left: 25px;
- width: 8px;
- height: 8px;
- }
- }
-
- .star {
- position: absolute;
- background-color: #ffffff;
- transition: all 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
- border-radius: 50%;
- }
-
- .star--1 {
- top: 10px;
- left: 35px;
- z-index: 0;
- width: 30px;
- height: 3px;
- }
-
- .star--2 {
- top: 18px;
- left: 28px;
- z-index: 1;
- width: 30px;
- height: 3px;
- }
-
- .star--3 {
- top: 27px;
- left: 40px;
- z-index: 0;
- width: 30px;
- height: 3px;
- }
-
- .star--4,
- .star--5,
- .star--6 {
- opacity: 0;
- transition: all 300ms 0 cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
- }
-
- .star--4 {
- top: 16px;
- left: 11px;
- z-index: 0;
- width: 2px;
- height: 2px;
- transform: translate3d(3px,0,0);
- }
-
- .star--5 {
- top: 32px;
- left: 17px;
- z-index: 0;
- width: 3px;
- height: 3px;
- transform: translate3d(3px,0,0);
- }
-
- .star--6 {
- top: 36px;
- left: 28px;
- z-index: 0;
- width: 2px;
- height: 2px;
- transform: translate3d(3px,0,0);
- }
-
- input:checked {
- + .toggle {
- background-color: #749DD6;
-
- > .before {
- color: var(--fg);
- }
-
- > .after {
- color: var(--accent);
- }
-
- .toggle__handler {
- background-color: #FFE5B5;
- transform: translate3d(40px, 0, 0) rotate(0);
-
- .crater { opacity: 1; }
- }
-
- .star--1 {
- width: 2px;
- height: 2px;
- }
-
- .star--2 {
- width: 4px;
- height: 4px;
- transform: translate3d(-5px, 0, 0);
- }
-
- .star--3 {
- width: 2px;
- height: 2px;
- transform: translate3d(-7px, 0, 0);
- }
-
- .star--4,
- .star--5,
- .star--6 {
- opacity: 1;
- transform: translate3d(0,0,0);
- }
-
- .star--4 {
- transition: all 300ms 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
- }
-
- .star--5 {
- transition: all 300ms 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
- }
-
- .star--6 {
- transition: all 300ms 400ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/settings/update.vue b/src/client/pages/settings/update.vue
deleted file mode 100644
index 8bc459e936..0000000000
--- a/src/client/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 #value v-if="meta">{{ meta.version }}</template>
- <template #value v-else><MkEllipsis/></template>
- </FormKeyValueView>
- </FormGroup>
- <FormGroup>
- <template #label>Misskey</template>
- <FormKeyValueView>
- <template #key>{{ $ts.latestVersion }}</template>
- <template #value v-if="releases">{{ releases[0].tag_name }}</template>
- <template #value v-else><MkEllipsis/></template>
- </FormKeyValueView>
- <template #caption v-if="releases"><MkTime :time="releases[0].published_at" mode="detail"/></template>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormSelect from '@client/components/form/select.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import * as os from '@client/os';
-import { version, instanceName } from '@client/config';
-import * as symbols from '@client/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/src/client/pages/settings/word-mute.vue b/src/client/pages/settings/word-mute.vue
deleted file mode 100644
index 53948b1b1e..0000000000
--- a/src/client/pages/settings/word-mute.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<template>
-<div>
- <MkTab v-model="tab">
- <option value="soft">{{ $ts._wordMute.soft }}</option>
- <option value="hard">{{ $ts._wordMute.hard }}</option>
- </MkTab>
- <FormBase>
- <div class="_debobigegoItem">
- <div v-show="tab === 'soft'">
- <FormInfo>{{ $ts._wordMute.softDescription }}</FormInfo>
- <FormTextarea v-model="softMutedWords">
- <span>{{ $ts._wordMute.muteWords }}</span>
- <template #desc>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template>
- </FormTextarea>
- </div>
- <div v-show="tab === 'hard'">
- <FormInfo>{{ $ts._wordMute.hardDescription }}</FormInfo>
- <FormTextarea v-model="hardMutedWords">
- <span>{{ $ts._wordMute.muteWords }}</span>
- <template #desc>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template>
- </FormTextarea>
- <FormKeyValueView v-if="hardWordMutedNotesCount != null">
- <template #key>{{ $ts._wordMute.mutedNotes }}</template>
- <template #value>{{ number(hardWordMutedNotesCount) }}</template>
- </FormKeyValueView>
- </div>
- </div>
- <FormButton @click="save()" primary inline :disabled="!changed"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormBase>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormTextarea from '@client/components/form/textarea.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import MkTab from '@client/components/tab.vue';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- FormTextarea,
- FormKeyValueView,
- MkTab,
- FormInfo,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.wordMute,
- icon: 'fas fa-comment-slash',
- bg: 'var(--bg)',
- },
- tab: 'soft',
- softMutedWords: '',
- hardMutedWords: '',
- hardWordMutedNotesCount: null,
- changed: false,
- }
- },
-
- watch: {
- softMutedWords: {
- handler() {
- this.changed = true;
- },
- deep: true
- },
- hardMutedWords: {
- handler() {
- this.changed = true;
- },
- deep: true
- },
- },
-
- async created() {
- this.softMutedWords = this.$store.state.mutedWords.map(x => x.join(' ')).join('\n');
- this.hardMutedWords = this.$i.mutedWords.map(x => x.join(' ')).join('\n');
-
- 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(' ')));
- await os.api('i/update', {
- mutedWords: this.hardMutedWords.trim().split('\n').map(x => x.trim().split(' ')),
- });
- this.changed = false;
- },
-
- number
- }
-});
-</script>
diff --git a/src/client/pages/share.vue b/src/client/pages/share.vue
deleted file mode 100644
index 70a9661dd0..0000000000
--- a/src/client/pages/share.vue
+++ /dev/null
@@ -1,184 +0,0 @@
-<template>
-<div class="">
- <section class="_section">
- <div class="_content">
- <XPostForm
- v-if="state === 'writing'"
- fixed
- :share="true"
- :initial-text="initialText"
- :initial-visibility="visibility"
- :initial-files="files"
- :initial-local-only="localOnly"
- :reply="reply"
- :renote="renote"
- :visible-users="visibleUsers"
- @posted="state = 'posted'"
- class="_panel"
- />
- <MkButton v-else-if="state === 'posted'" primary @click="close()" class="close">{{ $ts.close }}</MkButton>
- </div>
- </section>
-</div>
-</template>
-
-<script lang="ts">
-// SPECIFICATION: /src/docs/ja-JP/advanced/share-page.md
-
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import XPostForm from '@client/components/post-form.vue';
-import * as os from '@client/os';
-import { noteVisibilities } from '@/types';
-import { parseAcct } from '@/misc/acct';
-import * as symbols from '@client/symbols';
-import * as Misskey from 'misskey-js';
-
-export default defineComponent({
- components: {
- XPostForm,
- MkButton,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.share,
- icon: 'fas fa-share-alt'
- },
- state: 'fetching' as 'fetching' | 'writing' | 'posted',
-
- title: null as string | null,
- initialText: null as string | null,
- reply: null as Misskey.entities.Note | null,
- renote: null as Misskey.entities.Note | null,
- visibility: null as string | null,
- localOnly: null as boolean | null,
- files: [] as Misskey.entities.DriveFile[],
- visibleUsers: [] as Misskey.entities.User[],
- }
- },
-
- async created() {
- const urlParams = new URLSearchParams(window.location.search);
-
- this.title = urlParams.get('title');
- const text = urlParams.get('text');
- const url = urlParams.get('url');
-
- let noteText = '';
- if (this.title) noteText += `[ ${this.title} ]\n`;
- // Googleใƒ‹ใƒฅใƒผใ‚นๅฏพ็ญ–
- if (text?.startsWith(`${this.title}.\n`)) noteText += text.replace(`${this.title}.\n`, '');
- else if (text && this.title !== text) noteText += `${text}\n`;
- if (url) noteText += `${url}`;
- this.initialText = noteText.trim();
-
- const visibility = urlParams.get('visibility');
- if (noteVisibilities.includes(visibility)) {
- this.visibility = visibility;
- }
-
- if (this.visibility === 'specified') {
- const visibleUserIds = urlParams.get('visibleUserIds');
- const visibleAccts = urlParams.get('visibleAccts');
- await Promise.all(
- [
- ...(visibleUserIds ? visibleUserIds.split(',').map(userId => ({ userId })) : []),
- ...(visibleAccts ? visibleAccts.split(',').map(parseAcct) : [])
- ]
- // TypeScriptใฎๆŒ‡็คบ้€šใ‚Šใซๅค‰ๆ›ใ™ใ‚‹
- .map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q)
- .map(q => os.api('users/show', q)
- .then(user => {
- this.visibleUsers.push(user);
- }, () => {
- console.error(`Invalid user query: ${JSON.stringify(q)}`);
- })
- )
- );
- }
-
- const localOnly = urlParams.get('localOnly');
- if (localOnly === '0') this.localOnly = false;
- else if (localOnly === '1') this.localOnly = true;
-
- try {
- //#region Reply
- const replyId = urlParams.get('replyId');
- const replyUri = urlParams.get('replyUri');
- if (replyId) {
- this.reply = await os.api('notes/show', {
- noteId: replyId
- });
- } else if (replyUri) {
- const obj = await os.api('ap/show', {
- uri: replyUri
- });
- if (obj.type === 'Note') {
- this.reply = obj.object;
- }
- }
- //#endregion
-
- //#region Renote
- const renoteId = urlParams.get('renoteId');
- const renoteUri = urlParams.get('renoteUri');
- if (renoteId) {
- this.renote = await os.api('notes/show', {
- noteId: renoteId
- });
- } else if (renoteUri) {
- const obj = await os.api('ap/show', {
- uri: renoteUri
- });
- if (obj.type === 'Note') {
- this.renote = obj.object;
- }
- }
- //#endregion
-
- //#region Drive files
- const fileIds = urlParams.get('fileIds');
- if (fileIds) {
- await Promise.all(
- fileIds.split(',')
- .map(fileId => os.api('drive/files/show', { fileId })
- .then(file => {
- this.files.push(file);
- }, () => {
- console.error(`Failed to fetch a file ${fileId}`);
- })
- )
- );
- }
- //#endregion
- } catch (e) {
- os.dialog({
- type: 'error',
- title: e.message,
- text: e.name
- });
- }
-
- this.state = 'writing';
- },
-
- methods: {
- close() {
- window.close();
-
- // ้–‰ใ˜ใชใ‘ใ‚Œใฐ100msๅพŒใ‚ฟใ‚คใƒ ใƒฉใ‚คใƒณใซ
- setTimeout(() => {
- this.$router.push('/');
- }, 100);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.close {
- margin: 16px auto;
-}
-</style>
diff --git a/src/client/pages/signup-complete.vue b/src/client/pages/signup-complete.vue
deleted file mode 100644
index dada92031a..0000000000
--- a/src/client/pages/signup-complete.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<template>
-<div>
- {{ $ts.processing }}
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { login } from '@client/account';
-
-export default defineComponent({
- components: {
-
- },
-
- props: {
- code: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.signup,
- icon: 'fas fa-user'
- },
- }
- },
-
- mounted() {
- os.apiWithDialog('signup-pending', {
- code: this.code,
- }).then(res => {
- login(res.i, '/');
- });
- },
-
- methods: {
-
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/tag.vue b/src/client/pages/tag.vue
deleted file mode 100644
index 3ca9fe5c0c..0000000000
--- a/src/client/pages/tag.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<template>
-<div class="_section">
- <XNotes ref="notes" class="_content" :pagination="pagination" @before="before" @after="after"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import Progress from '@client/scripts/loading';
-import XNotes from '@client/components/notes.vue';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XNotes
- },
-
- props: {
- tag: {
- type: String,
- required: true
- }
- },
-
- 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();
- }
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- }
- }
-});
-</script>
diff --git a/src/client/pages/test.vue b/src/client/pages/test.vue
deleted file mode 100644
index fbab0112ed..0000000000
--- a/src/client/pages/test.vue
+++ /dev/null
@@ -1,259 +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 '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkRadio from '@client/components/form/radio.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/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/src/client/pages/theme-editor.vue b/src/client/pages/theme-editor.vue
deleted file mode 100644
index 3b10396ab8..0000000000
--- a/src/client/pages/theme-editor.vue
+++ /dev/null
@@ -1,306 +0,0 @@
-<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" @click="setBgColor(color)" class="color _button" :class="{ active: theme.props.bg === color.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" @click="setBgColor(color)" class="color _button" :class="{ active: theme.props.bg === color.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" @click="setAccentColor(color)" class="color rounded _button" :class="{ active: theme.props.accent === color }">
- <div class="preview" :style="{ background: color }"></div>
- </button>
- </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" @click="setFgColor(color)" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }">
- <div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div>
- </button>
- </div>
- </div>
- </div>
-
- <FormGroup v-if="codeEnabled">
- <FormTextarea v-model="themeCode" tall>
- <span>{{ $ts._theme.code }}</span>
- </FormTextarea>
- <FormButton @click="applyThemeCode" primary>{{ $ts.apply }}</FormButton>
- </FormGroup>
- <FormButton v-else @click="codeEnabled = true"><i class="fas fa-code"></i> {{ $ts.editCode }}</FormButton>
-
- <FormGroup v-if="descriptionEnabled">
- <FormTextarea v-model="description">
- <span>{{ $ts._theme.description }}</span>
- </FormTextarea>
- </FormGroup>
- <FormButton v-else @click="descriptionEnabled = true">{{ $ts.addDescription }}</FormButton>
-
- <FormGroup>
- <FormButton @click="showPreview"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton>
- <FormButton @click="saveAs" primary><i class="fas fa-save"></i> {{ $ts.saveAs }}</FormButton>
- </FormGroup>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } 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 '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-
-import { Theme, applyTheme, validateTheme, darkTheme, lightTheme } from '@client/scripts/theme';
-import { host } from '@client/config';
-import * as os from '@client/os';
-import { ColdDeviceStorage } from '@client/store';
-import { addTheme } from '@client/theme-store';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- FormTextarea,
- FormGroup,
- },
-
- 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);
- },
-
- beforeUnmount() {
- window.removeEventListener('beforeunload', this.beforeunload);
- },
-
- async beforeRouteLeave(to, from) {
- if (this.changed && !(await this.leaveConfirm())) {
- return false;
- }
- },
-
- methods: {
- beforeunload(e: BeforeUnloadEvent) {
- if (this.changed) {
- e.preventDefault();
- e.returnValue = '';
- }
- },
-
- async leaveConfirm(): Promise<boolean> {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$ts.leaveConfirm,
- showCancelButton: true
- });
- return !canceled;
- },
-
- showPreview() {
- os.pageWindow('preview');
- },
-
- 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;
-
- 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);
- }
- },
-
- setAccentColor(color) {
- this.theme.props.accent = color;
- },
-
- setFgColor(color) {
- this.theme.props.fg = this.theme.base === 'light' ? color.forLight : color.forDark;
- },
-
- apply() {
- this.themeCode = JSON5.stringify(this.theme, null, '\t');
- applyTheme(this.theme, false);
- this.changed = true;
- },
-
- applyThemeCode() {
- let parsed;
-
- try {
- parsed = JSON5.parse(this.themeCode);
- } catch (e) {
- os.dialog({
- type: 'error',
- text: this.$ts._theme.invalid
- });
- return;
- }
-
- this.theme = parsed;
- },
-
- async saveAs() {
- const { canceled, result: name } = await os.dialog({
- title: this.$ts.name,
- input: {
- allowEmpty: false
- }
- });
- if (canceled) return;
-
- 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.dialog({
- type: 'success',
- text: this.$t('_theme.installed', { name: this.theme.name })
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.cwepdizn {
- max-width: 800px;
- margin: 0 auto;
-
- > .colorPicker {
- > .colors {
- padding: 32px;
- text-align: center;
-
- > .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 {
- transform: scale(1.1);
- }
- }
-
- &.active {
- box-shadow: 0 0 0 2px var(--divider) inset;
- }
-
- &.rounded {
- border-radius: 999px;
-
- > .preview {
- border-radius: 999px;
- }
- }
-
- &.char {
- line-height: 42px;
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/timeline.tutorial.vue b/src/client/pages/timeline.tutorial.vue
deleted file mode 100644
index 620994c0da..0000000000
--- a/src/client/pages/timeline.tutorial.vue
+++ /dev/null
@@ -1,131 +0,0 @@
-<template>
-<div class="_card tbkwesmv">
- <div class="_title"><i class="fas fa-info-circle"></i> {{ $ts._tutorial.title }}</div>
- <div class="_content" v-if="tutorial === 0">
- <div>{{ $ts._tutorial.step1_1 }}</div>
- <div>{{ $ts._tutorial.step1_2 }}</div>
- <div>{{ $ts._tutorial.step1_3 }}</div>
- </div>
- <div class="_content" v-else-if="tutorial === 1">
- <div>{{ $ts._tutorial.step2_1 }}</div>
- <div>{{ $ts._tutorial.step2_2 }}</div>
- <MkA class="_link" to="/settings/profile">{{ $ts.editProfile }}</MkA>
- </div>
- <div class="_content" v-else-if="tutorial === 2">
- <div>{{ $ts._tutorial.step3_1 }}</div>
- <div>{{ $ts._tutorial.step3_2 }}</div>
- <div>{{ $ts._tutorial.step3_3 }}</div>
- <small>{{ $ts._tutorial.step3_4 }}</small>
- </div>
- <div class="_content" v-else-if="tutorial === 3">
- <div>{{ $ts._tutorial.step4_1 }}</div>
- <div>{{ $ts._tutorial.step4_2 }}</div>
- </div>
- <div class="_content" v-else-if="tutorial === 4">
- <div>{{ $ts._tutorial.step5_1 }}</div>
- <I18n :src="$ts._tutorial.step5_2" tag="div">
- <template #featured>
- <MkA class="_link" to="/featured">{{ $ts.featured }}</MkA>
- </template>
- <template #explore>
- <MkA class="_link" to="/explore">{{ $ts.explore }}</MkA>
- </template>
- </I18n>
- <div>{{ $ts._tutorial.step5_3 }}</div>
- <small>{{ $ts._tutorial.step5_4 }}</small>
- </div>
- <div class="_content" v-else-if="tutorial === 5">
- <div>{{ $ts._tutorial.step6_1 }}</div>
- <div>{{ $ts._tutorial.step6_2 }}</div>
- <div>{{ $ts._tutorial.step6_3 }}</div>
- </div>
- <div class="_content" v-else-if="tutorial === 6">
- <div>{{ $ts._tutorial.step7_1 }}</div>
- <I18n :src="$ts._tutorial.step7_2" tag="div">
- <template #help>
- <MkA class="_link" to="/docs">{{ $ts.help }}</MkA>
- </template>
- </I18n>
- <div>{{ $ts._tutorial.step7_3 }}</div>
- </div>
-
- <div class="_footer navigation">
- <div class="step">
- <button class="arrow _button" @click="tutorial--" :disabled="tutorial === 0">
- <i class="fas fa-chevron-left"></i>
- </button>
- <span>{{ tutorial + 1 }} / 7</span>
- <button class="arrow _button" @click="tutorial++" :disabled="tutorial === 6">
- <i class="fas fa-chevron-right"></i>
- </button>
- </div>
- <MkButton class="ok" @click="tutorial = -1" primary v-if="tutorial === 6"><i class="fas fa-check"></i> {{ $ts.gotIt }}</MkButton>
- <MkButton class="ok" @click="tutorial++" primary v-else><i class="fas fa-check"></i> {{ $ts.next }}</MkButton>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-
-export default defineComponent({
- components: {
- MkButton,
- },
-
- data() {
- return {
- }
- },
-
- computed: {
- tutorial: {
- get() { return this.$store.reactiveState.tutorial.value || 0; },
- set(value) { this.$store.set('tutorial', value); }
- },
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.tbkwesmv {
- > ._content {
- > small {
- opacity: 0.7;
- }
- }
-
- > .navigation {
- display: flex;
- flex-direction: row;
- align-items: baseline;
-
- > .step {
- > .arrow {
- padding: 4px;
-
- &:disabled {
- opacity: 0.5;
- }
-
- &:first-child {
- padding-right: 8px;
- }
-
- &:last-child {
- padding-left: 8px;
- }
- }
-
- > span {
- margin: 0 4px;
- }
- }
-
- > .ok {
- margin-left: auto;
- }
- }
-}
-</style>
diff --git a/src/client/pages/timeline.vue b/src/client/pages/timeline.vue
deleted file mode 100644
index 7b17d585f8..0000000000
--- a/src/client/pages/timeline.vue
+++ /dev/null
@@ -1,225 +0,0 @@
-<template>
-<div class="cmuxhskf" v-size="{ min: [800] }" v-hotkey.global="keymap">
- <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
- <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/>
-
- <div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
- <div class="tl _block">
- <XTimeline ref="tl" class="tl"
- :key="src"
- :src="src"
- :sound="true"
- @before="before()"
- @after="after()"
- @queue="queueUpdated"
- />
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import Progress from '@client/scripts/loading';
-import XTimeline from '@client/components/timeline.vue';
-import XPostForm from '@client/components/post-form.vue';
-import { scroll } from '@client/scripts/scroll';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- name: 'timeline',
-
- components: {
- XTimeline,
- XTutorial: defineAsyncComponent(() => import('./timeline.tutorial.vue')),
- XPostForm,
- },
-
- 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(); },
- }, {
- 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(); },
- }, {
- active: this.src === 'global',
- title: this.$ts._timelines.global,
- icon: 'fas fa-globe',
- iconOnly: true,
- onClick: () => { this.src = 'global'; this.saveSrc(); },
- }],
- })),
- };
- },
-
- computed: {
- keymap(): any {
- return {
- 't': this.focus
- };
- },
-
- isLocalTimelineAvailable(): boolean {
- return !this.$instance.disableLocalTimeline || this.$i.isModerator || this.$i.isAdmin;
- },
-
- 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;
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- },
-
- queueUpdated(q) {
- this.queue = q;
- },
-
- top() {
- scroll(this.$el, { top: 0 });
- },
-
- 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 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 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);
- },
-
- saveSrc() {
- this.$store.set('tl', {
- src: this.src,
- });
- },
-
- async timetravel() {
- const { canceled, result: date } = await os.dialog({
- title: this.$ts.date,
- input: {
- type: 'date'
- }
- });
- if (canceled) return;
-
- this.$refs.tl.timetravel(new Date(date));
- },
-
- focus() {
- (this.$refs.tl as any).focus();
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.cmuxhskf {
- padding: var(--margin);
-
- > .new {
- position: sticky;
- top: calc(var(--stickyTop, 0px) + 16px);
- z-index: 1000;
- width: 100%;
-
- > button {
- display: block;
- margin: var(--margin) auto 0 auto;
- padding: 8px 16px;
- border-radius: 32px;
- }
- }
-
- > .post-form {
- border-radius: var(--radius);
- }
-
- > .tl {
- background: var(--bg);
- border-radius: var(--radius);
- overflow: clip;
- }
-
- &.min-width_800px {
- max-width: 800px;
- margin: 0 auto;
- }
-}
-</style>
diff --git a/src/client/pages/user-ap-info.vue b/src/client/pages/user-ap-info.vue
deleted file mode 100644
index cbdff874ed..0000000000
--- a/src/client/pages/user-ap-info.vue
+++ /dev/null
@@ -1,124 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="apPromiseFactory" v-slot="{ result: ap }">
- <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 '@client/components/debobigego/object-view.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-import bytes from '@client/filters/bytes';
-import * as symbols from '@client/symbols';
-import { url } from '@client/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/src/client/pages/user-info.vue b/src/client/pages/user-info.vue
deleted file mode 100644
index bf67fc853a..0000000000
--- a/src/client/pages/user-info.vue
+++ /dev/null
@@ -1,245 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <div class="_debobigegoItem aeakzknw">
- <MkAvatar class="avatar" :user="user" :show-indicator="true"/>
- </div>
-
- <FormLink :to="userPage(user)">Profile</FormLink>
-
- <FormGroup>
- <FormKeyValueView>
- <template #key>Acct</template>
- <template #value><span class="_monospace">{{ acct(user) }}</span></template>
- </FormKeyValueView>
-
- <FormKeyValueView>
- <template #key>ID</template>
- <template #value><span class="_monospace">{{ user.id }}</span></template>
- </FormKeyValueView>
- </FormGroup>
-
- <FormGroup v-if="iAmModerator">
- <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" @update:modelValue="toggleModerator" v-model="moderator">{{ $ts.moderator }}</FormSwitch>
- <FormSwitch @update:modelValue="toggleSilence" v-model="silenced">{{ $ts.silence }}</FormSwitch>
- <FormSwitch @update:modelValue="toggleSuspend" v-model="suspended">{{ $ts.suspend }}</FormSwitch>
- </FormGroup>
-
- <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>
-
- <FormGroup>
- <FormLink :to="`/user-ap-info/${user.id}`">ActivityPub</FormLink>
-
- <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>
-
- <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>
-
- <FormObjectView tall :value="user">
- <span>Raw</span>
- </FormObjectView>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { computed, defineAsyncComponent, defineComponent } from 'vue';
-import FormObjectView from '@client/components/debobigego/object-view.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-import bytes from '@client/filters/bytes';
-import * as symbols from '@client/symbols';
-import { url } from '@client/config';
-import { userPage, acct } from '@client/filters/user';
-
-export default defineComponent({
- components: {
- FormBase,
- FormTextarea,
- FormSwitch,
- FormObjectView,
- FormButton,
- FormLink,
- FormGroup,
- FormKeyValueView,
- FormSuspense,
- },
-
- props: {
- userId: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => ({
- title: this.user ? acct(this.user) : this.$ts.userInfo,
- icon: 'fas fa-info-circle',
- actions: this.user ? [this.user.url ? {
- text: this.user.url,
- icon: 'fas fa-external-link-alt',
- handler: () => {
- window.open(this.user.url, '_blank');
- }
- } : undefined].filter(x => x !== undefined) : [],
- })),
- init: null,
- user: null,
- info: null,
- moderator: false,
- silenced: false,
- suspended: false,
- }
- },
-
- computed: {
- iAmModerator(): boolean {
- return this.$i && (this.$i.isAdmin || this.$i.isModerator);
- }
- },
-
- watch: {
- userId: {
- handler() {
- this.init = this.createFetcher();
- },
- immediate: true
- }
- },
-
- methods: {
- number,
- bytes,
- userPage,
- acct,
-
- createFetcher() {
- if (this.iAmModerator) {
- return () => Promise.all([os.api('users/show', {
- userId: this.userId
- }), os.api('admin/show-user', {
- userId: this.userId
- })]).then(([user, info]) => {
- this.user = user;
- this.info = info;
- this.moderator = this.info.isModerator;
- this.silenced = this.info.isSilenced;
- this.suspended = this.info.isSuspended;
- });
- } else {
- return () => os.api('users/show', {
- userId: this.userId
- }).then((user) => {
- this.user = user;
- });
- }
- },
-
- refreshUser() {
- this.init = this.createFetcher();
- },
-
- async updateRemoteUser() {
- await os.apiWithDialog('federation/update-remote-user', { userId: this.user.id });
- this.refreshUser();
- },
-
- async resetPassword() {
- const { password } = await os.api('admin/reset-password', {
- userId: this.user.id,
- });
-
- os.dialog({
- type: 'success',
- text: this.$t('newPasswordIs', { password })
- });
- },
-
- async toggleSilence(v) {
- const confirm = await os.dialog({
- type: 'warning',
- showCancelButton: true,
- text: v ? this.$ts.silenceConfirm : this.$ts.unsilenceConfirm,
- });
- if (confirm.canceled) {
- this.silenced = !v;
- } else {
- await os.api(v ? 'admin/silence-user' : 'admin/unsilence-user', { userId: this.user.id });
- await this.refreshUser();
- }
- },
-
- async toggleSuspend(v) {
- const confirm = await os.dialog({
- type: 'warning',
- showCancelButton: true,
- text: v ? this.$ts.suspendConfirm : this.$ts.unsuspendConfirm,
- });
- if (confirm.canceled) {
- this.suspended = !v;
- } else {
- await os.api(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: this.user.id });
- await this.refreshUser();
- }
- },
-
- async toggleModerator(v) {
- await os.api(v ? 'admin/moderators/add' : 'admin/moderators/remove', { userId: this.user.id });
- await this.refreshUser();
- },
-
- async deleteAllFiles() {
- const confirm = await os.dialog({
- type: 'warning',
- showCancelButton: true,
- text: this.$ts.deleteAllFilesConfirm,
- });
- if (confirm.canceled) return;
- const process = async () => {
- await os.api('admin/delete-all-files-of-a-user', { userId: this.user.id });
- os.success();
- };
- await process().catch(e => {
- os.dialog({
- type: 'error',
- text: e.toString()
- });
- });
- await this.refreshUser();
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.aeakzknw {
- > .avatar {
- display: block;
- margin: 0 auto;
- width: 64px;
- height: 64px;
- }
-}
-</style>
diff --git a/src/client/pages/user-list-timeline.vue b/src/client/pages/user-list-timeline.vue
deleted file mode 100644
index b5e37d4843..0000000000
--- a/src/client/pages/user-list-timeline.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<template>
-<div class="eqqrhokj" v-hotkey.global="keymap" v-size="{ min: [800] }">
- <div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
- <div class="tl _block">
- <XTimeline ref="tl" class="tl"
- :key="listId"
- src="list"
- :list="listId"
- :sound="true"
- @before="before()"
- @after="after()"
- @queue="queueUpdated"
- />
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import Progress from '@client/scripts/loading';
-import XTimeline from '@client/components/timeline.vue';
-import { scroll } from '@client/scripts/scroll';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XTimeline,
- },
-
- props: {
- listId: {
- type: String,
- required: true
- }
- },
-
- data() {
- return {
- list: null,
- queue: 0,
- [symbols.PAGE_INFO]: computed(() => this.list ? {
- title: this.list.name,
- icon: 'fas fa-list-ul',
- bg: 'var(--bg)',
- actions: [{
- icon: 'fas fa-calendar-alt',
- text: this.$ts.jumpToSpecifiedDate,
- handler: this.timetravel
- }, {
- icon: 'fas fa-cog',
- text: this.$ts.settings,
- handler: this.settings
- }],
- } : null),
- };
- },
-
- computed: {
- keymap(): any {
- return {
- 't': this.focus
- };
- },
- },
-
- watch: {
- listId: {
- async handler() {
- this.list = await os.api('users/lists/show', {
- listId: this.listId
- });
- },
- immediate: true
- }
- },
-
- methods: {
- before() {
- Progress.start();
- },
-
- after() {
- Progress.done();
- },
-
- queueUpdated(q) {
- this.queue = q;
- },
-
- top() {
- scroll(this.$el, { top: 0 });
- },
-
- settings() {
- this.$router.push(`/my/lists/${this.listId}`);
- },
-
- async timetravel() {
- const { canceled, result: date } = await os.dialog({
- title: this.$ts.date,
- input: {
- type: 'date'
- }
- });
- if (canceled) return;
-
- this.$refs.tl.timetravel(new Date(date));
- },
-
- focus() {
- (this.$refs.tl as any).focus();
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.eqqrhokj {
- padding: var(--margin);
-
- > .new {
- position: sticky;
- top: calc(var(--stickyTop, 0px) + 16px);
- z-index: 1000;
- width: 100%;
-
- > button {
- display: block;
- margin: var(--margin) auto 0 auto;
- padding: 8px 16px;
- border-radius: 32px;
- }
- }
-
- > .tl {
- background: var(--bg);
- border-radius: var(--radius);
- overflow: clip;
- }
-
- &.min-width_800px {
- max-width: 800px;
- margin: 0 auto;
- }
-}
-</style>
diff --git a/src/client/pages/user/clips.vue b/src/client/pages/user/clips.vue
deleted file mode 100644
index 53ee554383..0000000000
--- a/src/client/pages/user/clips.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<template>
-<div>
- <MkPagination :pagination="pagination" #default="{items}" ref="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>
- </MkA>
- </MkPagination>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-
-export default defineComponent({
- components: {
- MkPagination,
- },
-
- props: {
- user: {
- type: Object,
- required: true
- },
- },
-
- data() {
- return {
- pagination: {
- endpoint: 'users/clips',
- limit: 20,
- params: {
- userId: this.user.id,
- }
- },
- };
- },
-
- watch: {
- user() {
- this.$refs.list.reload();
- }
- },
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/user/follow-list.vue b/src/client/pages/user/follow-list.vue
deleted file mode 100644
index 1f5ab5993c..0000000000
--- a/src/client/pages/user/follow-list.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<div>
- <MkPagination :pagination="pagination" #default="{items}" class="mk-following-or-followers" ref="list">
- <div class="users _isolated">
- <MkUserInfo class="user" v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :user="user" :key="user.id"/>
- </div>
- </MkPagination>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkUserInfo from '@client/components/user-info.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkUserInfo,
- },
-
- props: {
- user: {
- type: Object,
- required: true
- },
- type: {
- type: String,
- required: true
- },
- },
-
- 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();
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-following-or-followers {
- > .users {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
- grid-gap: var(--margin);
- }
-}
-</style>
diff --git a/src/client/pages/user/gallery.vue b/src/client/pages/user/gallery.vue
deleted file mode 100644
index c21b3e6428..0000000000
--- a/src/client/pages/user/gallery.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<template>
-<div>
- <MkPagination :pagination="pagination" #default="{items}">
- <div class="jrnovfpt">
- <MkGalleryPostPreview v-for="post in items" :post="post" :key="post.id" class="post"/>
- </div>
- </MkPagination>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkGalleryPostPreview from '@client/components/gallery-post-preview.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkGalleryPostPreview,
- },
-
- props: {
- user: {
- type: Object,
- required: true
- },
- },
-
- data() {
- return {
- pagination: {
- endpoint: 'users/gallery/posts',
- limit: 6,
- params: () => ({
- userId: this.user.id
- })
- },
- };
- },
-
- watch: {
- user() {
- this.$refs.list.reload();
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.jrnovfpt {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
- grid-gap: 12px;
- margin: var(--margin);
-}
-</style>
diff --git a/src/client/pages/user/index.activity.vue b/src/client/pages/user/index.activity.vue
deleted file mode 100644
index be85c252e8..0000000000
--- a/src/client/pages/user/index.activity.vue
+++ /dev/null
@@ -1,34 +0,0 @@
-<template>
-<MkContainer>
- <template #header><i class="fas fa-chart-bar" style="margin-right: 0.5em;"></i>{{ $ts.activity }}</template>
-
- <div style="padding: 8px;">
- <MkChart src="per-user-notes" :args="{ user, withoutAll: true }" span="day" :limit="limit" :stacked="true" :detailed="false" :aspect-ratio="6"/>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@client/os';
-import MkContainer from '@client/components/ui/container.vue';
-import MkChart from '@client/components/chart.vue';
-
-export default defineComponent({
- components: {
- MkContainer,
- MkChart,
- },
- props: {
- user: {
- type: Object,
- required: true
- },
- limit: {
- type: Number,
- required: false,
- default: 40
- }
- },
-});
-</script>
diff --git a/src/client/pages/user/index.photos.vue b/src/client/pages/user/index.photos.vue
deleted file mode 100644
index 5029c3feec..0000000000
--- a/src/client/pages/user/index.photos.vue
+++ /dev/null
@@ -1,107 +0,0 @@
-<template>
-<MkContainer :max-height="300" :foldable="true">
- <template #header><i class="fas fa-image" style="margin-right: 0.5em;"></i>{{ $ts.images }}</template>
- <div class="ujigsodd">
- <MkLoading v-if="fetching"/>
- <div class="stream" v-if="!fetching && images.length > 0">
- <MkA v-for="image in images"
- class="img"
- :to="notePage(image.note)"
- :key="image.id"
- >
- <ImgWithBlurhash :hash="image.blurhash" :src="thumbnail(image.file)" :alt="image.name" :title="image.name"/>
- </MkA>
- </div>
- <p class="empty" v-if="!fetching && images.length == 0">{{ $ts.nothing }}</p>
- </div>
-</MkContainer>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { getStaticImageUrl } from '@client/scripts/get-static-image-url';
-import notePage from '@client/filters/note';
-import * as os from '@client/os';
-import MkContainer from '@client/components/ui/container.vue';
-import ImgWithBlurhash from '@client/components/img-with-blurhash.vue';
-
-export default defineComponent({
- components: {
- MkContainer,
- ImgWithBlurhash,
- },
- props: {
- user: {
- type: Object,
- required: true
- },
- },
- data() {
- return {
- fetching: true,
- images: [],
- };
- },
- mounted() {
- const image = [
- 'image/jpeg',
- 'image/png',
- 'image/gif',
- 'image/apng',
- 'image/vnd.mozilla.apng',
- ];
- os.api('users/notes', {
- userId: this.user.id,
- fileType: image,
- excludeNsfw: this.$store.state.nsfw !== 'ignore',
- limit: 10,
- }).then(notes => {
- for (const note of notes) {
- for (const file of note.files) {
- this.images.push({
- note,
- file
- });
- }
- }
- this.fetching = false;
- });
- },
- methods: {
- thumbnail(image: any): string {
- return this.$store.state.disableShowingAnimatedImages
- ? getStaticImageUrl(image.thumbnailUrl)
- : image.thumbnailUrl;
- },
- notePage
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.ujigsodd {
- padding: 8px;
-
- > .stream {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
- grid-gap: 6px;
-
- > .img {
- height: 128px;
- border-radius: 6px;
- overflow: clip;
- }
- }
-
- > .empty {
- margin: 0;
- padding: 16px;
- text-align: center;
-
- > i {
- margin-right: 4px;
- }
- }
-}
-</style>
diff --git a/src/client/pages/user/index.timeline.vue b/src/client/pages/user/index.timeline.vue
deleted file mode 100644
index c3444f26f6..0000000000
--- a/src/client/pages/user/index.timeline.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-<template>
-<div class="yrzkoczt" v-sticky-container>
- <MkTab v-model="with_" 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)"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XNotes from '@client/components/notes.vue';
-import MkTab from '@client/components/tab.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XNotes,
- MkTab,
- },
-
- props: {
- user: {
- type: Object,
- required: true,
- },
- },
-
- watch: {
- user() {
- this.$refs.timeline.reload();
- },
-
- with_() {
- this.$refs.timeline.reload();
- },
- },
-
- 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),
- })
- }
- };
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.yrzkoczt {
- > .tab {
- margin: calc(var(--margin) / 2) 0;
- padding: calc(var(--margin) / 2) 0;
- background: var(--bg);
- }
-}
-</style>
diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue
deleted file mode 100644
index 04585f3fd0..0000000000
--- a/src/client/pages/user/index.vue
+++ /dev/null
@@ -1,829 +0,0 @@
-<template>
-<div>
-<transition name="fade" mode="out-in">
- <div class="ftskorzw wide" v-if="user && narrow === false">
- <MkRemoteCaution v-if="user.host != null" :href="user.url"/>
-
- <div class="banner-container" :style="style">
- <div class="banner" ref="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 class="followed" v-if="$i && $i.id != user.id && user.isFollowed"><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 class="field" v-if="user.location">
- <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt>
- <dd class="value">{{ user.location }}</dd>
- </dl>
- <dl class="field" v-if="user.birthday">
- <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 class="fields" v-if="user.fields.length > 0">
- <dl class="field" v-for="(field, i) in user.fields" :key="i">
- <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 :user="user" :key="user.id" class="_gap"/>
- <XPhotos :user="user" :key="user.id" class="_gap"/>
- </div>
- <div class="main">
- <div class="actions">
- <button @click="menu" class="menu _button"><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" class="note _gap" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/>
- </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 class="ftskorzw narrow" v-size="{ max: [500] }">
- <!-- 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="_block main" :key="user.id">
- <div class="banner-container" :style="style">
- <div class="banner" ref="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 class="followed" v-if="$i && $i.id != user.id && user.isFollowed">{{ $ts.followsYou }}</span>
- <div class="actions" v-if="$i">
- <button @click="menu" class="menu _button"><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 class="field" v-if="user.location">
- <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt>
- <dd class="value">{{ user.location }}</dd>
- </dl>
- <dl class="field" v-if="user.birthday">
- <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 class="fields" v-if="user.fields.length > 0">
- <dl class="field" v-for="(field, i) in user.fields" :key="i">
- <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 :to="userPage(user)" :class="{ active: page === 'index' }" v-click-anime>
- <b>{{ number(user.notesCount) }}</b>
- <span>{{ $ts.notes }}</span>
- </MkA>
- <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }" v-click-anime>
- <b>{{ number(user.followingCount) }}</b>
- <span>{{ $ts.following }}</span>
- </MkA>
- <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }" v-click-anime>
- <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" class="note _block" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/>
- </div>
- <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo>
- <XPhotos :user="user" :key="user.id"/>
- <XActivity :user="user" :key="user.id" style="margin-top: var(--margin);"/>
- </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>
- </div>
- </MkSpacer>
- <MkError v-else-if="error" @retry="fetch()"/>
- <MkLoading v-else/>
-</transition>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import * as age from 's-age';
-import XUserTimeline from './index.timeline.vue';
-import XNote from '@client/components/note.vue';
-import MkFollowButton from '@client/components/follow-button.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import MkRemoteCaution from '@client/components/remote-caution.vue';
-import MkTab from '@client/components/tab.vue';
-import MkInfo from '@client/components/ui/info.vue';
-import Progress from '@client/scripts/loading';
-import { parseAcct } from '@/misc/acct';
-import { getScrollPosition } from '@client/scripts/scroll';
-import { getUserMenu } from '@client/scripts/get-user-menu';
-import number from '@client/filters/number';
-import { userPage, acct as getAcct } from '@client/filters/user';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XUserTimeline,
- XNote,
- MkFollowButton,
- MkContainer,
- MkRemoteCaution,
- MkFolder,
- MkTab,
- MkInfo,
- XFollowList: defineAsyncComponent(() => import('./follow-list.vue')),
- XReactions: defineAsyncComponent(() => import('./reactions.vue')),
- XClips: defineAsyncComponent(() => import('./clips.vue')),
- XPages: defineAsyncComponent(() => import('./pages.vue')),
- XGallery: defineAsyncComponent(() => import('./gallery.vue')),
- XPhotos: defineAsyncComponent(() => import('./index.photos.vue')),
- XActivity: defineAsyncComponent(() => import('./index.activity.vue')),
- },
-
- props: {
- acct: {
- type: String,
- required: true
- },
- page: {
- type: String,
- required: false,
- default: 'index'
- }
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: computed(() => this.user ? {
- icon: 'fas fa-user',
- title: this.user.name ? `${this.user.name} (@${this.user.username})` : `@${this.user.username}`,
- subtitle: `@${getAcct(this.user)}`,
- userName: this.user,
- avatar: this.user,
- path: `/@${this.user.username}`,
- share: {
- title: this.user.name,
- },
- bg: 'var(--bg)',
- tabs: [{
- active: this.page === 'index',
- title: this.$ts.overview,
- icon: 'fas fa-home',
- onClick: () => { this.$router.push('/@' + getAcct(this.user)); },
- }, ...(this.$i && (this.$i.id === this.user.id)) || this.user.publicReactions ? [{
- active: this.page === 'reactions',
- title: this.$ts.reaction,
- icon: 'fas fa-laugh',
- onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/reactions'); },
- }] : [], {
- active: this.page === 'clips',
- title: this.$ts.clips,
- icon: 'fas fa-paperclip',
- onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/clips'); },
- }, {
- active: this.page === 'pages',
- title: this.$ts.pages,
- icon: 'fas fa-file-alt',
- onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/pages'); },
- }, {
- active: this.page === 'gallery',
- title: this.$ts.gallery,
- icon: 'fas fa-icons',
- onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/gallery'); },
- }],
- } : null),
- user: null,
- error: null,
- parallaxAnimationId: null,
- narrow: null,
- };
- },
-
- computed: {
- style(): any {
- if (this.user.bannerUrl == null) return {};
- return {
- backgroundImage: `url(${ this.user.bannerUrl })`
- };
- },
-
- age(): number {
- return age(this.user.birthday);
- }
- },
-
- watch: {
- acct: 'fetch'
- },
-
- created() {
- this.fetch();
- },
-
- mounted() {
- window.requestAnimationFrame(this.parallaxLoop);
- this.narrow = true//this.$el.clientWidth < 1000;
- },
-
- beforeUnmount() {
- window.cancelAnimationFrame(this.parallaxAnimationId);
- },
-
- methods: {
- getAcct,
-
- fetch() {
- if (this.acct == null) return;
- this.user = null;
- Progress.start();
- os.api('users/show', parseAcct(this.acct)).then(user => {
- this.user = user;
- }).catch(e => {
- this.error = e;
- }).finally(() => {
- Progress.done();
- });
- },
-
- menu(ev) {
- os.popupMenu(getUserMenu(this.user), ev.currentTarget || ev.target);
- },
-
- parallaxLoop() {
- this.parallaxAnimationId = window.requestAnimationFrame(this.parallaxLoop);
- this.parallax();
- },
-
- parallax() {
- const banner = this.$refs.banner as any;
- if (banner == null) return;
-
- const top = getScrollPosition(this.$el);
-
- if (top < 0) return;
-
- const z = 1.75; // ๅฅฅ่กŒใ(ๅฐใ•ใ„ใปใฉๅฅฅ)
- const pos = -(top / z);
- 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
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
- transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
- opacity: 0;
-}
-
-.ftskorzw.wide {
-
- > .banner-container {
- position: relative;
- height: 300px;
- 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;
- }
- }
-
- > .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;
- }
- }
-
- > .status {
- display: flex;
- padding: 20px 16px;
- border-top: solid 0.5px var(--divider);
- font-size: 90%;
-
- > a {
- flex: 1;
- text-align: center;
-
- &.active {
- color: var(--accent);
- }
-
- &:hover {
- text-decoration: none;
- }
-
- > b {
- display: block;
- line-height: 16px;
- }
-
- > span {
- font-size: 75%;
- }
- }
- }
-
- > .description {
- padding: 20px 16px;
- border-top: solid 0.5px var(--divider);
- font-size: 90%;
- }
-
- > .fields {
- padding: 20px 16px;
- border-top: solid 0.5px var(--divider);
- font-size: 90%;
-
- > .field {
- display: flex;
- padding: 0;
- margin: 0;
- align-items: center;
-
- &:not(:last-child) {
- margin-bottom: 8px;
- }
-
- > .name {
- width: 30%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- font-weight: bold;
- }
-
- > .value {
- width: 70%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- margin: 0;
- }
- }
- }
- }
-
- > .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;
- text-align: center;
- border-bottom: solid 3px transparent;
-
- &:hover {
- text-decoration: none;
- }
-
- &.active {
- color: var(--accent);
- border-bottom-color: var(--accent);
- }
-
- &:not(.active):hover {
- color: var(--fgHighlighted);
- }
-
- > .icon {
- margin-right: 6px;
- }
- }
-
- > .actions {
- display: flex;
- align-items: center;
- margin-left: auto;
-
- > .menu {
- padding: 12px 16px;
- }
- }
- }
- }
- }
-}
-
-.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;
- }
-
- > .fade {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 78px;
- background: linear-gradient(transparent, rgba(#000, 0.7));
- }
-
- > .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;
- }
-
- > .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;
-
- > .menu {
- vertical-align: bottom;
- height: 31px;
- width: 31px;
- color: #fff;
- text-shadow: 0 0 8px #000;
- font-size: 16px;
- }
-
- > .koudoku {
- margin-left: 4px;
- vertical-align: bottom;
- }
- }
-
- > .title {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- padding: 0 0 8px 154px;
- box-sizing: border-box;
- color: #fff;
-
- > .name {
- display: block;
- margin: 0;
- line-height: 32px;
- font-weight: bold;
- font-size: 1.8em;
- text-shadow: 0 0 8px #000;
- }
-
- > .bottom {
- > * {
- display: inline-block;
- margin-right: 16px;
- line-height: 20px;
- opacity: 0.8;
-
- &.username {
- font-weight: bold;
- }
- }
- }
- }
- }
-
- > .title {
- display: none;
- text-align: center;
- padding: 50px 8px 16px 8px;
- font-weight: bold;
- border-bottom: solid 0.5px var(--divider);
-
- > .bottom {
- > * {
- display: inline-block;
- margin-right: 8px;
- opacity: 0.8;
- }
- }
- }
-
- > .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;
- }
- }
-
- > .fields {
- padding: 24px;
- font-size: 0.9em;
- border-top: solid 0.5px var(--divider);
-
- > .field {
- display: flex;
- padding: 0;
- margin: 0;
- align-items: center;
-
- &:not(:last-child) {
- margin-bottom: 8px;
- }
-
- > .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;
- }
- }
-
- &.system > .field > .name {
- }
- }
-
- > .status {
- display: flex;
- padding: 24px;
- border-top: solid 0.5px var(--divider);
-
- > a {
- flex: 1;
- 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;
- }
-
- > .title {
- display: none;
- }
- }
-
- > .title {
- display: block;
- }
-
- > .avatar {
- top: 90px;
- left: 0;
- right: 0;
- width: 92px;
- height: 92px;
- margin: auto;
- }
-
- > .description {
- padding: 16px;
- text-align: center;
- }
-
- > .fields {
- padding: 16px;
- }
-
- > .status {
- padding: 16px;
- }
- }
-
- > .contents {
- > .nav {
- font-size: 80%;
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/user/pages.vue b/src/client/pages/user/pages.vue
deleted file mode 100644
index ece418cf62..0000000000
--- a/src/client/pages/user/pages.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-<template>
-<div>
- <MkPagination :pagination="pagination" #default="{items}" ref="list">
- <MkPagePreview v-for="page in items" :page="page" :key="page.id" class="_gap"/>
- </MkPagination>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagePreview from '@client/components/page-preview.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkPagePreview,
- },
-
- 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();
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/user/reactions.vue b/src/client/pages/user/reactions.vue
deleted file mode 100644
index 5ac7e01027..0000000000
--- a/src/client/pages/user/reactions.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<template>
-<div>
- <MkPagination :pagination="pagination" #default="{items}" ref="list">
- <div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap afdcfbfb">
- <div class="header">
- <MkAvatar class="avatar" :user="user"/>
- <MkReactionIcon class="reaction" :reaction="item.type" :custom-emojis="item.note.emojis" :no-style="true"/>
- <MkTime :time="item.createdAt" class="createdAt"/>
- </div>
- <MkNote :note="item.note" @update:note="updated(note, $event)" :key="item.id"/>
- </div>
- </MkPagination>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkNote from '@client/components/note.vue';
-import MkReactionIcon from '@client/components/reaction-icon.vue';
-
-export default defineComponent({
- components: {
- MkPagination,
- MkNote,
- MkReactionIcon,
- },
-
- 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();
- }
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.afdcfbfb {
- > .header {
- display: flex;
- align-items: center;
- padding: 8px 16px;
- margin-bottom: 8px;
- border-bottom: solid 2px var(--divider);
-
- > .avatar {
- width: 24px;
- height: 24px;
- margin-right: 8px;
- }
-
- > .reaction {
- width: 32px;
- height: 32px;
- }
-
- > .createdAt {
- margin-left: auto;
- }
- }
-}
-</style>
diff --git a/src/client/pages/v.vue b/src/client/pages/v.vue
deleted file mode 100644
index 4440e8070e..0000000000
--- a/src/client/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 '@client/config';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'Misskey',
- icon: null
- },
- version,
- }
- },
-});
-</script>
diff --git a/src/client/pages/welcome.entrance.a.vue b/src/client/pages/welcome.entrance.a.vue
deleted file mode 100644
index 13f0993793..0000000000
--- a/src/client/pages/welcome.entrance.a.vue
+++ /dev/null
@@ -1,320 +0,0 @@
-<template>
-<div class="rsqzvsbo" v-if="meta">
- <div class="top">
- <MkFeaturedPhotos class="bg"/>
- <XTimeline class="tl"/>
- <div class="shape1"></div>
- <div class="shape2"></div>
- <img src="/static-assets/client/misskey.svg" class="misskey"/>
- <div class="emojis">
- <MkEmoji :normal="true" :no-style="true" emoji="๐Ÿ‘"/>
- <MkEmoji :normal="true" :no-style="true" emoji="โค"/>
- <MkEmoji :normal="true" :no-style="true" emoji="๐Ÿ˜†"/>
- <MkEmoji :normal="true" :no-style="true" emoji="๐ŸŽ‰"/>
- <MkEmoji :normal="true" :no-style="true" emoji="๐Ÿฎ"/>
- </div>
- <div class="main _panel">
- <div class="bg">
- <div class="fade"></div>
- </div>
- <div class="fg">
- <h1>
- <!-- ่ƒŒๆ™ฏ่‰ฒใซใ‚ˆใฃใฆใฏใƒญใ‚ดใŒ่ฆ‹ใˆใชใใชใ‚‹ใฎใงใจใ‚Šใ‚ใˆใš็„กๅŠนใซ -->
- <!-- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> -->
- <span class="text">{{ instanceName }}</span>
- </h1>
- <div class="about">
- <div class="desc" v-html="meta.description || $ts.headlineMisskey"></div>
- </div>
- <div class="action">
- <MkButton @click="signup()" inline gradate data-cy-signup style="margin-right: 12px;">{{ $ts.signup }}</MkButton>
- <MkButton @click="signin()" inline data-cy-signin>{{ $ts.login }}</MkButton>
- </div>
- <div class="status" v-if="onlineUsersCount && stats">
- <div>
- <I18n :src="$ts.nUsers" text-tag="span" class="users">
- <template #n><b>{{ number(stats.originalUsersCount) }}</b></template>
- </I18n>
- <I18n :src="$ts.nNotes" text-tag="span" class="notes">
- <template #n><b>{{ number(stats.originalNotesCount) }}</b></template>
- </I18n>
- </div>
- <I18n :src="$ts.onlineUsersCount" text-tag="span" class="online">
- <template #n><b>{{ onlineUsersCount }}</b></template>
- </I18n>
- </div>
- <button class="_button _acrylic menu" @click="showMenu"><i class="fas fa-ellipsis-h"></i></button>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { toUnicode } from 'punycode/';
-import XSigninDialog from '@client/components/signin-dialog.vue';
-import XSignupDialog from '@client/components/signup-dialog.vue';
-import MkButton from '@client/components/ui/button.vue';
-import XNote from '@client/components/note.vue';
-import MkFeaturedPhotos from '@client/components/featured-photos.vue';
-import XTimeline from './welcome.timeline.vue';
-import { host, instanceName } from '@client/config';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-
-export default defineComponent({
- components: {
- MkButton,
- XNote,
- MkFeaturedPhotos,
- XTimeline,
- },
-
- data() {
- return {
- host: toUnicode(host),
- instanceName,
- meta: null,
- stats: null,
- tags: [],
- onlineUsersCount: null,
- };
- },
-
- created() {
- os.api('meta', { detail: true }).then(meta => {
- this.meta = meta;
- });
-
- os.api('stats').then(stats => {
- this.stats = stats;
- });
-
- os.api('get-online-users-count').then(res => {
- this.onlineUsersCount = res.count;
- });
-
- os.api('hashtags/list', {
- sort: '+mentionedLocalUsers',
- limit: 8
- }).then(tags => {
- this.tags = tags;
- });
- },
-
- methods: {
- signin() {
- os.popup(XSigninDialog, {
- autoSet: true
- }, {}, 'closed');
- },
-
- signup() {
- os.popup(XSignupDialog, {
- autoSet: true
- }, {}, 'closed');
- },
-
- showMenu(ev) {
- os.popupMenu([{
- text: this.$t('aboutX', { x: instanceName }),
- icon: 'fas fa-info-circle',
- action: () => {
- os.pageWindow('/about');
- }
- }, {
- text: this.$ts.aboutMisskey,
- icon: 'fas fa-info-circle',
- action: () => {
- os.pageWindow('/about-misskey');
- }
- }, null, {
- text: this.$ts.help,
- icon: 'fas fa-question-circle',
- action: () => {
- os.pageWindow('/docs');
- }
- }], ev.currentTarget || ev.target);
- },
-
- number
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.rsqzvsbo {
- > .top {
- display: flex;
- text-align: center;
- min-height: 100vh;
- box-sizing: border-box;
- padding: 16px;
-
- > .bg {
- position: absolute;
- top: 0;
- right: 0;
- width: 80%; // 100%ใ‹ใ‚‰shapeใฎๅน…ใ‚’ๅผ•ใ„ใฆใ„ใ‚‹
- height: 100%;
- }
-
- > .tl {
- position: absolute;
- top: 0;
- bottom: 0;
- right: 64px;
- margin: auto;
- width: 500px;
- height: calc(100% - 128px);
- overflow: hidden;
- -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
- mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
-
- @media (max-width: 1200px) {
- display: none;
- }
- }
-
- > .shape1 {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: var(--accent);
- clip-path: polygon(0% 0%, 45% 0%, 20% 100%, 0% 100%);
- }
- > .shape2 {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: var(--accent);
- clip-path: polygon(0% 0%, 25% 0%, 35% 100%, 0% 100%);
- opacity: 0.5;
- }
-
- > .misskey {
- position: absolute;
- top: 42px;
- left: 42px;
- width: 160px;
-
- @media (max-width: 450px) {
- width: 130px;
- }
- }
-
- > .emojis {
- position: absolute;
- bottom: 32px;
- left: 35px;
-
- > * {
- margin-right: 8px;
- }
-
- @media (max-width: 1200px) {
- display: none;
- }
- }
-
- > .main {
- position: relative;
- width: min(480px, 100%);
- margin: auto auto auto 128px;
- box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
-
- @media (max-width: 1200px) {
- margin: auto;
- }
-
- > .bg {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 128px;
- background-position: center;
- background-size: cover;
- opacity: 0.75;
-
- > .fade {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 128px;
- background: linear-gradient(0deg, var(--panel), var(--X15));
- }
- }
-
- > .fg {
- position: relative;
- z-index: 1;
-
- > h1 {
- display: block;
- margin: 0;
- padding: 32px 32px 24px 32px;
- font-size: 1.5em;
-
- > .logo {
- vertical-align: bottom;
- max-height: 120px;
- max-width: min(100%, 300px);
- }
- }
-
- > .about {
- padding: 0 32px;
- }
-
- > .action {
- padding: 32px;
-
- > * {
- line-height: 28px;
- }
- }
-
- > .status {
- border-top: solid 0.5px var(--divider);
- padding: 32px;
- font-size: 90%;
-
- > div {
- > span:not(:last-child) {
- padding-right: 1em;
- margin-right: 1em;
- border-right: solid 0.5px var(--divider);
- }
- }
-
- > .online {
- ::v-deep(b) {
- color: #41b781;
- }
-
- ::v-deep(span) {
- opacity: 0.7;
- }
- }
- }
-
- > .menu {
- position: absolute;
- top: 16px;
- right: 16px;
- width: 32px;
- height: 32px;
- border-radius: 8px;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/welcome.entrance.b.vue b/src/client/pages/welcome.entrance.b.vue
deleted file mode 100644
index 163fc1e35f..0000000000
--- a/src/client/pages/welcome.entrance.b.vue
+++ /dev/null
@@ -1,236 +0,0 @@
-<template>
-<div class="rsqzvsbo" v-if="meta">
- <div class="top">
- <MkFeaturedPhotos class="bg"/>
- <XTimeline class="tl"/>
- <div class="shape"></div>
- <div class="main">
- <h1>
- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span>
- </h1>
- <div class="about">
- <div class="desc" v-html="meta.description || $ts.headlineMisskey"></div>
- </div>
- <div class="action">
- <MkButton class="signup" @click="signup()" inline gradate>{{ $ts.signup }}</MkButton>
- <MkButton class="signin" @click="signin()" inline>{{ $ts.login }}</MkButton>
- </div>
- <div class="status" v-if="onlineUsersCount && stats">
- <div>
- <I18n :src="$ts.nUsers" text-tag="span" class="users">
- <template #n><b>{{ number(stats.originalUsersCount) }}</b></template>
- </I18n>
- <I18n :src="$ts.nNotes" text-tag="span" class="notes">
- <template #n><b>{{ number(stats.originalNotesCount) }}</b></template>
- </I18n>
- </div>
- <I18n :src="$ts.onlineUsersCount" text-tag="span" class="online">
- <template #n><b>{{ onlineUsersCount }}</b></template>
- </I18n>
- </div>
- </div>
- <img src="/static-assets/client/misskey.svg" class="misskey"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { toUnicode } from 'punycode/';
-import XSigninDialog from '@client/components/signin-dialog.vue';
-import XSignupDialog from '@client/components/signup-dialog.vue';
-import MkButton from '@client/components/ui/button.vue';
-import XNote from '@client/components/note.vue';
-import MkFeaturedPhotos from '@client/components/featured-photos.vue';
-import XTimeline from './welcome.timeline.vue';
-import { host, instanceName } from '@client/config';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-
-export default defineComponent({
- components: {
- MkButton,
- XNote,
- XTimeline,
- MkFeaturedPhotos,
- },
-
- data() {
- return {
- host: toUnicode(host),
- instanceName,
- meta: null,
- stats: null,
- tags: [],
- onlineUsersCount: null,
- };
- },
-
- created() {
- os.api('meta', { detail: true }).then(meta => {
- this.meta = meta;
- });
-
- os.api('stats').then(stats => {
- this.stats = stats;
- });
-
- os.api('get-online-users-count').then(res => {
- this.onlineUsersCount = res.count;
- });
-
- os.api('hashtags/list', {
- sort: '+mentionedLocalUsers',
- limit: 8
- }).then(tags => {
- this.tags = tags;
- });
- },
-
- methods: {
- signin() {
- os.popup(XSigninDialog, {
- autoSet: true
- }, {}, 'closed');
- },
-
- signup() {
- os.popup(XSignupDialog, {
- autoSet: true
- }, {}, 'closed');
- },
-
- showMenu(ev) {
- os.popupMenu([{
- text: this.$t('aboutX', { x: instanceName }),
- icon: 'fas fa-info-circle',
- action: () => {
- os.pageWindow('/about');
- }
- }, {
- text: this.$ts.aboutMisskey,
- icon: 'fas fa-info-circle',
- action: () => {
- os.pageWindow('/about-misskey');
- }
- }, null, {
- text: this.$ts.help,
- icon: 'fas fa-question-circle',
- action: () => {
- os.pageWindow('/docs');
- }
- }], ev.currentTarget || ev.target);
- },
-
- number
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.rsqzvsbo {
- > .top {
- min-height: 100vh;
- box-sizing: border-box;
-
- > .bg {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- }
-
- > .tl {
- position: absolute;
- top: 0;
- bottom: 0;
- right: 64px;
- margin: auto;
- width: 500px;
- height: calc(100% - 128px);
- overflow: hidden;
- -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
- mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
- }
-
- > .shape {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: var(--accent);
- clip-path: polygon(0% 0%, 40% 0%, 22% 100%, 0% 100%);
- }
-
- > .misskey {
- position: absolute;
- bottom: 64px;
- left: 64px;
- width: 160px;
- }
-
- > .main {
- position: relative;
- width: min(450px, 100%);
- padding: 64px;
- color: #fff;
- font-size: 1.1em;
-
- @media (max-width: 1200px) {
- margin: auto;
- }
-
- > h1 {
- display: block;
- margin: 0 0 32px 0;
- padding: 0;
-
- > .logo {
- vertical-align: bottom;
- max-height: 100px;
- }
- }
-
- > .about {
- padding: 0;
- }
-
- > .action {
- margin: 32px 0;
-
- > * {
- line-height: 32px;
- }
-
- > .signup {
- background: var(--panel);
- color: var(--fg);
- }
-
- > .signin {
- background: var(--accent);
- color: inherit;
- }
- }
-
- > .status {
- margin: 32px 0;
- border-top: solid 1px rgba(255, 255, 255, 0.5);
- font-size: 90%;
-
- > div {
- padding: 16px 0;
-
- > span:not(:last-child) {
- padding-right: 1em;
- margin-right: 1em;
- border-right: solid 1px rgba(255, 255, 255, 0.5);
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/welcome.entrance.c.vue b/src/client/pages/welcome.entrance.c.vue
deleted file mode 100644
index bf1c9b1998..0000000000
--- a/src/client/pages/welcome.entrance.c.vue
+++ /dev/null
@@ -1,305 +0,0 @@
-<template>
-<div class="rsqzvsbo" v-if="meta">
- <div class="top">
- <MkFeaturedPhotos class="bg"/>
- <div class="fade"></div>
- <div class="emojis">
- <MkEmoji :normal="true" :no-style="true" emoji="๐Ÿ‘"/>
- <MkEmoji :normal="true" :no-style="true" emoji="โค"/>
- <MkEmoji :normal="true" :no-style="true" emoji="๐Ÿ˜†"/>
- <MkEmoji :normal="true" :no-style="true" emoji="๐ŸŽ‰"/>
- <MkEmoji :normal="true" :no-style="true" emoji="๐Ÿฎ"/>
- </div>
- <div class="main">
- <img src="/static-assets/client/misskey.svg" class="misskey"/>
- <div class="form _panel">
- <div class="bg">
- <div class="fade"></div>
- </div>
- <div class="fg">
- <h1>
- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span>
- </h1>
- <div class="about">
- <div class="desc" v-html="meta.description || $ts.headlineMisskey"></div>
- </div>
- <div class="action">
- <MkButton @click="signup()" inline gradate>{{ $ts.signup }}</MkButton>
- <MkButton @click="signin()" inline>{{ $ts.login }}</MkButton>
- </div>
- <div class="status" v-if="onlineUsersCount && stats">
- <div>
- <I18n :src="$ts.nUsers" text-tag="span" class="users">
- <template #n><b>{{ number(stats.originalUsersCount) }}</b></template>
- </I18n>
- <I18n :src="$ts.nNotes" text-tag="span" class="notes">
- <template #n><b>{{ number(stats.originalNotesCount) }}</b></template>
- </I18n>
- </div>
- <I18n :src="$ts.onlineUsersCount" text-tag="span" class="online">
- <template #n><b>{{ onlineUsersCount }}</b></template>
- </I18n>
- </div>
- <button class="_button _acrylic menu" @click="showMenu"><i class="fas fa-ellipsis-h"></i></button>
- </div>
- </div>
- <nav class="nav">
- <MkA to="/announcements">{{ $ts.announcements }}</MkA>
- <MkA to="/explore">{{ $ts.explore }}</MkA>
- <MkA to="/channels">{{ $ts.channel }}</MkA>
- <MkA to="/featured">{{ $ts.featured }}</MkA>
- </nav>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { toUnicode } from 'punycode/';
-import XSigninDialog from '@client/components/signin-dialog.vue';
-import XSignupDialog from '@client/components/signup-dialog.vue';
-import MkButton from '@client/components/ui/button.vue';
-import XNote from '@client/components/note.vue';
-import MkFeaturedPhotos from '@client/components/featured-photos.vue';
-import XTimeline from './welcome.timeline.vue';
-import { host, instanceName } from '@client/config';
-import * as os from '@client/os';
-import number from '@client/filters/number';
-
-export default defineComponent({
- components: {
- MkButton,
- XNote,
- MkFeaturedPhotos,
- XTimeline,
- },
-
- data() {
- return {
- host: toUnicode(host),
- instanceName,
- meta: null,
- stats: null,
- tags: [],
- onlineUsersCount: null,
- };
- },
-
- created() {
- os.api('meta', { detail: true }).then(meta => {
- this.meta = meta;
- });
-
- os.api('stats').then(stats => {
- this.stats = stats;
- });
-
- os.api('get-online-users-count').then(res => {
- this.onlineUsersCount = res.count;
- });
-
- os.api('hashtags/list', {
- sort: '+mentionedLocalUsers',
- limit: 8
- }).then(tags => {
- this.tags = tags;
- });
- },
-
- methods: {
- signin() {
- os.popup(XSigninDialog, {
- autoSet: true
- }, {}, 'closed');
- },
-
- signup() {
- os.popup(XSignupDialog, {
- autoSet: true
- }, {}, 'closed');
- },
-
- showMenu(ev) {
- os.popupMenu([{
- text: this.$t('aboutX', { x: instanceName }),
- icon: 'fas fa-info-circle',
- action: () => {
- os.pageWindow('/about');
- }
- }, {
- text: this.$ts.aboutMisskey,
- icon: 'fas fa-info-circle',
- action: () => {
- os.pageWindow('/about-misskey');
- }
- }, null, {
- text: this.$ts.help,
- icon: 'fas fa-question-circle',
- action: () => {
- os.pageWindow('/docs');
- }
- }], ev.currentTarget || ev.target);
- },
-
- number
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.rsqzvsbo {
- > .top {
- display: flex;
- text-align: center;
- min-height: 100vh;
- box-sizing: border-box;
- padding: 16px;
-
- > .bg {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- }
-
- > .fade {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.25);
- }
-
- > .emojis {
- position: absolute;
- bottom: 32px;
- left: 35px;
-
- > * {
- margin-right: 8px;
- }
-
- @media (max-width: 1200px) {
- display: none;
- }
- }
-
- > .main {
- position: relative;
- width: min(460px, 100%);
- margin: auto;
-
- > .misskey {
- width: 150px;
- margin-bottom: 16px;
-
- @media (max-width: 450px) {
- width: 130px;
- }
- }
-
- > .form {
- position: relative;
- box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
-
- > .bg {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 128px;
- background-position: center;
- background-size: cover;
- opacity: 0.75;
-
- > .fade {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 128px;
- background: linear-gradient(0deg, var(--panel), var(--X15));
- }
- }
-
- > .fg {
- position: relative;
- z-index: 1;
-
- > h1 {
- display: block;
- margin: 0;
- padding: 32px 32px 24px 32px;
-
- > .logo {
- vertical-align: bottom;
- max-height: 120px;
- }
- }
-
- > .about {
- padding: 0 32px;
- }
-
- > .action {
- padding: 32px;
-
- > * {
- line-height: 28px;
- }
- }
-
- > .status {
- border-top: solid 0.5px var(--divider);
- padding: 32px;
- font-size: 90%;
-
- > div {
- > span:not(:last-child) {
- padding-right: 1em;
- margin-right: 1em;
- border-right: solid 0.5px var(--divider);
- }
- }
-
- > .online {
- ::v-deep(b) {
- color: #41b781;
- }
-
- ::v-deep(span) {
- opacity: 0.7;
- }
- }
- }
-
- > .menu {
- position: absolute;
- top: 16px;
- right: 16px;
- width: 32px;
- height: 32px;
- border-radius: 8px;
- }
- }
- }
-
- > .nav {
- position: relative;
- z-index: 2;
- margin-top: 20px;
- color: #fff;
- text-shadow: 0 0 8px black;
- font-size: 0.9em;
-
- > *:not(:last-child) {
- margin-right: 1.5em;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/welcome.setup.vue b/src/client/pages/welcome.setup.vue
deleted file mode 100644
index dfefecc8fa..0000000000
--- a/src/client/pages/welcome.setup.vue
+++ /dev/null
@@ -1,102 +0,0 @@
-<template>
-<form class="mk-setup" @submit.prevent="submit()">
- <h1>Welcome to Misskey!</h1>
- <div>
- <p>{{ $ts.intro }}</p>
- <MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" spellcheck="false" required data-cy-admin-username>
- <template #label>{{ $ts.username }}</template>
- <template #prefix>@</template>
- <template #suffix>@{{ host }}</template>
- </MkInput>
- <MkInput v-model="password" type="password" data-cy-admin-password>
- <template #label>{{ $ts.password }}</template>
- <template #prefix><i class="fas fa-lock"></i></template>
- </MkInput>
- <footer>
- <MkButton primary type="submit" :disabled="submitting" data-cy-admin-ok>
- {{ submitting ? $ts.processing : $ts.done }}<MkEllipsis v-if="submitting"/>
- </MkButton>
- </footer>
- </div>
-</form>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import { host } from '@client/config';
-import * as os from '@client/os';
-import { login } from '@client/account';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- },
-
- data() {
- return {
- username: '',
- password: '',
- submitting: false,
- host,
- }
- },
-
- methods: {
- submit() {
- if (this.submitting) return;
- this.submitting = true;
-
- os.api('admin/accounts/create', {
- username: this.username,
- password: this.password,
- }).then(res => {
- return login(res.token);
- }).catch(() => {
- this.submitting = false;
-
- os.dialog({
- type: 'error',
- text: this.$ts.somethingHappened
- });
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-setup {
- border-radius: var(--radius);
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
- overflow: hidden;
- max-width: 500px;
- margin: 32px auto;
-
- > h1 {
- margin: 0;
- font-size: 1.5em;
- text-align: center;
- padding: 32px;
- background: var(--accent);
- color: #fff;
- }
-
- > div {
- padding: 32px;
- background: var(--panel);
-
- > p {
- margin-top: 0;
- }
-
- > footer {
- > * {
- margin: 0 auto;
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/welcome.timeline.vue b/src/client/pages/welcome.timeline.vue
deleted file mode 100644
index bd07ac78db..0000000000
--- a/src/client/pages/welcome.timeline.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-<div class="civpbkhh">
- <div class="scrollbox" ref="scroll" v-bind:class="{ scroll: isScrolling }">
- <div v-for="note in notes" class="note">
- <div class="content _panel">
- <div class="body">
- <MkA class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><i class="fas fa-reply"></i></MkA>
- <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
- <MkA class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
- </div>
- <div v-if="note.files.length > 0" class="richcontent">
- <XMediaList :media-list="note.files"/>
- </div>
- <div v-if="note.poll">
- <XPoll :note="note" :readOnly="true" />
- </div>
- </div>
- <XReactionsViewer :note="note" ref="reactionsViewer"/>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XReactionsViewer from '@client/components/reactions-viewer.vue';
-import XMediaList from '@client/components/media-list.vue';
-import XPoll from '@client/components/poll.vue';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XReactionsViewer,
- XMediaList,
- XPoll
- },
-
- data() {
- return {
- notes: [],
- isScrolling: false,
- }
- },
-
- created() {
- os.api('notes/featured').then(notes => {
- this.notes = notes;
- });
- },
-
- updated() {
- if (this.$refs.scroll.clientHeight > window.innerHeight) {
- this.isScrolling = true;
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-@keyframes scroll {
- 0% {
- transform: translate3d(0, 0, 0);
- }
- 5% {
- transform: translate3d(0, 0, 0);
- }
- 75% {
- transform: translate3d(0, calc(-100% + 90vh), 0);
- }
- 90% {
- transform: translate3d(0, calc(-100% + 90vh), 0);
- }
-}
-
-.civpbkhh {
- text-align: right;
-
- > .scrollbox {
- &.scroll {
- animation: scroll 45s linear infinite;
- }
-
- > .note {
- margin: 16px 0 16px auto;
-
- > .content {
- padding: 16px;
- margin: 0 0 0 auto;
- max-width: max-content;
- border-radius: 16px;
-
- > .richcontent {
- min-width: 250px;
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/welcome.vue b/src/client/pages/welcome.vue
deleted file mode 100644
index b6a715830d..0000000000
--- a/src/client/pages/welcome.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-<template>
-<div v-if="meta">
- <XSetup v-if="meta.requireSetup"/>
- <XEntrance v-else/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XSetup from './welcome.setup.vue';
-import XEntrance from './welcome.entrance.a.vue';
-import { instanceName } from '@client/config';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- XSetup,
- XEntrance,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: instanceName,
- icon: null
- },
- meta: null
- }
- },
-
- created() {
- os.api('meta', { detail: true }).then(meta => {
- this.meta = meta;
- });
- }
-});
-</script>