* enhance: pizzaxでstreamingのuser storage updateイベントを監視して更新 (#8095)

* wip

* wip?

* ?

* streamingのuser storage updateイベントを監視して更新

* 必要な時以外はストレージを更新しない

* fix?

* wip

* fix

* fix

* fix pizzax (#8099)

* Update CONTRIBUTING.md

* スコープの判定を厳密に (#8100)

* enhance(client): tweak ui

* enhance(client): tweak ui

* wip (#8101)

* Revert "revert d53795184"

This reverts commit aedbab17cc.

* fix

d53795184c (r62707827)

* update deps

* tweak client

* tweak ui

* lint

* refactor(server): use insert instead of save

* refactor(server): use insert instead of save

* tweak ui

* enhance: 許可されていないファイルタイプでは、オブジェクトストレージのファイル名に拡張子を付与しないように (#8108)

* 許可されていないファイルタイプでは、オブジェクトストレージのファイル名に拡張子を付与しないように

* add comment

* tweak ui

* tweak ui

* tweak ui

* clean up

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* 非ログイン時にエラーを吐くconsole.logを除去 (#8119)

* refactor(client): use composition api

* clean up

* refactor(client): use composition api

* refactor(client): use composition api

* refactor(client): use composition api

* refactor(client): use composition api

* refactor(client): use composition api

* clean up

* refactor(client): use composition api

* refactor(client): use composition api

* refactor(client): use composition api

* remove unused components

* bye room

* refactor: Widgetのcomposition api移行 (#8125)

* wip

* wip

* wip

* wip

* wip

* wip

* fix

* fix

* bye chat ui

* wip: migrate paging components to composition api

#7681

* wip: migrate paging components to composition api

* wip: migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* fix

* refactor: Composition APIへ移行 (#8121)

* components/abuse-report-window.vue

* use <script setup>

* ✌️

* components/analog-clock.vue

* wip components/autocomplete.vue

* ✌️

* ✌️

* fix

* wip components/captcha.vue

* clean up

* components/channel-follow-button

* components/channel-preview.vue

* components/core-core.vue

* components/code.vue

* wip components/date-separated-list.vue

* fix

* fix autocomplete.vue

* ✌️

* remove global property

* use <script setup>

* components/dialog.vue

* clena up

* fix dialog.vue

* Resolve https://github.com/misskey-dev/misskey/pull/8121#discussion_r781250966

* fix

* bye reversi

* Fix The unauthenticated git protocol on port 9418 is no longer supported. (#8139)

* feat: multiple emojis editing

* feat: emojis import

* git add忘れ

* Update CHANGELOG.md

* clean up

* refactor

* wip: refactor(client): migrate paging components to composition api

* refactor

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* 🎨

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* clean up

* refactor(client): specify global scope

* refactor: disallow some variable names

* refactor: more common name

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

Fix #8155

* Fix #8151 (#8152)

* refactor

* refactor: APIエンドポイントファイルの定義を良い感じにする (#8154)

* Fix API Schema Error

* Delete SimpleSchema/SimpleObj
and Move schemas to dedicated files

* Userのスキーマを分割してみる

* define packMany type

* add ,

* Ensure enum schema and Make "as const" put once

* test?

* Revert "test?"

This reverts commit 97dc9bfa70851bfb7d1cf38e883f8df20fb78b79.

* Revert "Fix API Schema Error"

This reverts commit 21b6176d974ed8e3eb73723ad21a105c5d297323.

* ✌️

* clean up

* test?

* wip

* wip

* better schema def

* ✌️

* fix

* add minLength property

* wip

* wip

* wip

* anyOf/oneOf/allOfに対応? ~ relation.ts

* refactor!

* Define MinimumSchema

* wip

* wip

* anyOf/oneOf/allOfが動作するようにUnionSchemaTypeを修正

* anyOf/oneOf/allOfが動作するようにUnionSchemaTypeを修正

* Update packages/backend/src/misc/schema.ts

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* fix

* array oneOfをより正確な型に

* array oneOfをより正確な型に

* wip

* ✌️

* なんかもういろいろ

* remove

* very good schema

* api schema

* wip

* refactor: awaitAllの型定義を変えてみる

* fix

* specify types in awaitAll

* specify types in awaitAll

* ✌️

* wip

* ...

* ✌️

* AllowDateはやめておく

* 不必要なoptional: false, nullable: falseを廃止

* Packedが展開されないように

* 続packed

* wip

* define note type

* wip

* UserDetailedをMeDetailedかUserDetailedNotMeかを区別できるように

* wip

* wip

* wip specify user type of other schemas

* ok

* convertSchemaToOpenApiSchemaを改修

* convertSchemaToOpenApiSchemaを改修

* Fix

* fix

* ✌️

* wip

* 分割代入ではなくallOfで定義するように

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* refactor: Composition APIへ移行 (#8138)

* components/drive-file-thumbnail.vue

* components/drive-select-dialog.vue

* components/drive-window.vue

* wip

* wip drive.file.vue, drive.vue

* fix prop

* wip(

* components/drive.folder.vue

* maybe ok

* ✌️

* fix variable

* FIX FOLDER VARIABLE

* components/emoji-picker-dialog.vue

* Hate `$emit`

* hate global property

* components/emoji-picker-window.vue

* components/emoji-picker.section.vue

* fix

* fixx

* wip components/emoji-picker.vue

* fix

* defineExpose

* ユニコード絵文字の型をもっといい感じに

* components/featured-photos.vue

* components/follow-button.vue

* forgot-password.vue

* forgot-password.vue

* 🎨

* fix

* モバイル画面で表示更新直後にヘッダーメニューをタップしてもポップアップにならないようにする (#8160)

* fix #8158

* refactor

* refactor

* refactor(server): use insert instead of save

* feat(server): store mime type of webpublic

* refactor(server): use named export

* fix: proxyでsvgをpngに変換するように (#8106)

* wip

* revert send-drive-file change

* fix

* Update packages/backend/src/server/proxy/proxy-media.ts

Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>

Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>

* send-drive-file svg as png (#8107)

* post-form.vue (#8164)

* feat(server): add more metadata for emoji export

* fix: code url in documentation (#8117)

It seems this was not changed while refactoring the modules apart.

* enhance: Forward report (#8001)

* implement sending AP Flag object

Optionally allow a user to select to forward a report about a remote
user to the other instance. This is added in a backwards-compatible way.

* add locale string

* forward report only for moderators

* add switch to moderator UI to forward report

* fix report note url

* return forwarded status from API

apparently forgot to carry this over from my testing environment

* object in Flag activity has to be an array

For correct interoperability with Pleroma the "object" property of the Flag
activity has to be an array.

This array will in the future also hold the link to respective notes, so it
makes sense to correct this on our side.

* Update get-note-menu.ts

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* enhance: e2eテストをできるだけ改良してみた (#8159)

* update docker image?

* 続

* serial run delete from "${table}" cascade

* use cypress official github action

* refuse install by cypress action

* clean up

* use wait?

* use more wait?

* Revert "use more wait?"

This reverts commit 18d0fcae9c7d8f98a4cafb4a846a031ece57350c.

* Revert "use wait?"

This reverts commit 5aa8feec9cdc3e2f79e566249f0a0eff6c0df6a0.

* fix

* test

* test

* log?

* 握りつぶしてみる

* clean up

* env?

* clean up?

* disable video

* add comment

* remove test

* 成功?

* test browser

* nodeインストール無効化

* node16.13.0-chrome95-ff94

* node.js復活

* ?

* ちょっと戻してみる

* chrome?

* cross browser test2

* --shm-size=2g

* artifact?

* misskey.local?

* firefoxはあきらめる

* not headless?

* oops

* fix

* ??

* test1

* if?

* fail-fast: false

* headless: false

* easy error ignoreing describe

* エラーの解消
とちょっとリファクター

* add browser name to artifact

* Install mplayer for FireFox

* no wait?

* タイムアウトを甘くしてみる

* firefoxをあきらめる(n回目)

* remove timeout setting

* wait復活

* Update basic.js

* Update index.js

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* update deps

* refactor

* fix(#8133): hCaptcha の reCAPTCHA 互換挙動を無効化する (#8135)

* fix(#8133): hCaptcha の reCAPTCHA 互換挙動を無効化する

* Update packages/client/src/components/captcha.vue

* fix: hCaptcha host

Co-authored-by: tamaina <tamaina@hotmail.co.jp>

* update local copy of file when describing (#8131)

* feat: increase files limit for note

#8062

* enhance: convert svg to png of custom emojis

* Update CHANGELOG.md

* update dep

* feat(client): make possible to switch account instantly in post form

* 投稿したらアカウントを元に戻すように

* chore(client): add tooltip

* wip: refactor(client): migrate components to composition api

* chore(client): add #misskey button

* fix(client): タイムラインのkeep-aliveが効かなくなっているのを修正

* NodeInfo にユーザー数と投稿数の情報を追加する (#8126)

* Unifying Misskey-specific IRIs in JSON-LD `@context` Resolve #8116  (#8178)

* Unifying Misskey-specific IRIs in JSON-LD `@context` Resolve #8116

* CHANGELOG

* refactor, enhance: ドライブ引数のオブジェクト化, 追加時のcomment指定 (#8180)

* refactor: ドライブの引数をオブジェクト化する Resolve #8177

* Resolve #8181

* fix

* archivePath

* fix: アップロードエラー時の処理を修正 (#8182)

* アップロードのエラー応答で詰むのを修正

* CHANGELOG

* fix: change keypress to keydown (#8192)

* Update docker-compose.yml (#8163)

Fix sometime es may cannot start
refer:https://m.html.cn/site/111215825993025.html

* disable animations on more transitions (#8112)

* 🎨

* Update CHANGELOG.md

* refactor(backend): use insert instead of save

* Update CONTRIBUTING.md

* enhance: Improve poll-editor UI + composition port (#8186)

* Poll editor UI changes

Use a horizontal layout when possible, wrap to vertical when constrained

* Port poll-editor to composition API

* Fix poll-editor `get` time calcs

* fix

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* refactor

* Update CONTRIBUTING.md

* 🎨

* Update extensions.json

* Fix pop-out bug (#8170)

* refactor: fix type

* refactor(backend): fix type

* refactor(backend): fix type

* New Crowdin updates (#8096)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* enhance: MediaListでは、サーバーで許可された形式しか表示しないように (#8113)

* wip

* fix

* update vue

* Update CHANGELOG.md

* 12.102.0

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: xianon <xianon@hotmail.co.jp>
Co-authored-by: Johann150 <johann.galle@protonmail.com>
Co-authored-by: nullobsi <me@nullob.si>
Co-authored-by: Hyunseung Jeon <dogdriip@gmail.com>
Co-authored-by: 老兄 <lao__xong@outlook.com>
Co-authored-by: Derek <skeh@is.nota.live>
Co-authored-by: Kainoa Kanter <44733677+ThatOneCalculator@users.noreply.github.com>
This commit is contained in:
NullCat
2022-01-27 00:50:28 +09:00
committed by GitHub
parent af6d52e4c8
commit 0fb9a438f9
955 changed files with 16618 additions and 34411 deletions

View File

@ -1,68 +1,61 @@
<template>
<MkLoading v-if="!loaded" />
<MkLoading v-if="!loaded"/>
<transition :name="$store.state.animation ? 'zoom' : ''" appear>
<div v-show="loaded" class="mjndxjch">
<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
<p><b><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</b></p>
<p v-if="version === meta.version">{{ $ts.pageLoadErrorDescription }}</p>
<p v-else-if="serverIsDead">{{ $ts.serverIsDead }}</p>
<p><b><i class="fas fa-exclamation-triangle"></i> {{ i18n.locale.pageLoadError }}</b></p>
<p v-if="meta && (version === meta.version)">{{ i18n.locale.pageLoadErrorDescription }}</p>
<p v-else-if="serverIsDead">{{ i18n.locale.serverIsDead }}</p>
<template v-else>
<p>{{ $ts.newVersionOfClientAvailable }}</p>
<p>{{ $ts.youShouldUpgradeClient }}</p>
<MkButton class="button primary" @click="reload">{{ $ts.reload }}</MkButton>
<p>{{ i18n.locale.newVersionOfClientAvailable }}</p>
<p>{{ i18n.locale.youShouldUpgradeClient }}</p>
<MkButton class="button primary" @click="reload">{{ i18n.locale.reload }}</MkButton>
</template>
<p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p>
<p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.locale.troubleshooting }}</MkA></p>
<p v-if="error" class="error">ERROR: {{ error }}</p>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { } from 'vue';
import * as misskey from 'misskey-js';
import MkButton from '@/components/ui/button.vue';
import * as symbols from '@/symbols';
import { version } from '@/config';
import * as os from '@/os';
import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
MkButton,
},
props: {
error: {
required: false,
}
},
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.error,
icon: 'fas fa-exclamation-triangle'
},
loaded: false,
serverIsDead: false,
meta: {} as any,
version,
};
},
created() {
os.api('meta', {
detail: false
}).then(meta => {
this.loaded = true;
this.serverIsDead = false;
this.meta = meta;
localStorage.setItem('v', meta.version);
}, () => {
this.loaded = true;
this.serverIsDead = true;
});
},
methods: {
reload() {
unisonReload();
},
const props = withDefaults(defineProps<{
error?: Error;
}>(), {
});
let loaded = $ref(false);
let serverIsDead = $ref(false);
let meta = $ref<misskey.entities.LiteInstanceMetadata | null>(null);
os.api('meta', {
detail: false,
}).then(res => {
loaded = true;
serverIsDead = false;
meta = res;
localStorage.setItem('v', res.version);
}, () => {
loaded = true;
serverIsDead = true;
});
function reload() {
unisonReload();
}
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.error,
icon: 'fas fa-exclamation-triangle',
},
});
</script>

View File

@ -2,9 +2,5 @@
<MkLoading/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@/os';
export default defineComponent({});
<script lang="ts" setup>
</script>

View File

@ -3,36 +3,39 @@
<MkSpacer :content-max="600" :margin-min="20">
<div class="_formRoot znqjceqz">
<div id="debug"></div>
<div ref="about" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }">
<div ref="containerEl" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }">
<img src="/client-assets/about-icon.png" alt="" class="icon" draggable="false" @load="iconLoaded" @click="gravity"/>
<div class="misskey">Misskey</div>
<div class="version">v{{ version }}</div>
<span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span>
</div>
<div class="_formBlock" style="text-align: center;">
{{ $ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ $ts.learnMore }}</a>
{{ i18n.locale._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.locale.learnMore }}</a>
</div>
<div class="_formBlock" style="text-align: center;">
<MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton>
</div>
<FormSection>
<div class="_formLinks">
<FormLink to="https://github.com/misskey-dev/misskey" external>
<template #icon><i class="fas fa-code"></i></template>
{{ $ts._aboutMisskey.source }}
{{ i18n.locale._aboutMisskey.source }}
<template #suffix>GitHub</template>
</FormLink>
<FormLink to="https://crowdin.com/project/misskey" external>
<template #icon><i class="fas fa-language"></i></template>
{{ $ts._aboutMisskey.translation }}
{{ i18n.locale._aboutMisskey.translation }}
<template #suffix>Crowdin</template>
</FormLink>
<FormLink to="https://www.patreon.com/syuilo" external>
<template #icon><i class="fas fa-hand-holding-medical"></i></template>
{{ $ts._aboutMisskey.donate }}
{{ i18n.locale._aboutMisskey.donate }}
<template #suffix>Patreon</template>
</FormLink>
</div>
</FormSection>
<FormSection>
<template #label>{{ $ts._aboutMisskey.contributors }}</template>
<template #label>{{ i18n.locale._aboutMisskey.contributors }}</template>
<div class="_formLinks">
<FormLink to="https://github.com/syuilo" external>@syuilo</FormLink>
<FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink>
@ -44,27 +47,30 @@
<FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink>
<FormLink to="https://github.com/marihachi" external>@marihachi</FormLink>
</div>
<template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ $ts._aboutMisskey.allContributors }}</MkLink></template>
<template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ i18n.locale._aboutMisskey.allContributors }}</MkLink></template>
</FormSection>
<FormSection>
<template #label><Mfm text="$[jelly ❤]"/> {{ $ts._aboutMisskey.patrons }}</template>
<template #label><Mfm text="$[jelly ❤]"/> {{ i18n.locale._aboutMisskey.patrons }}</template>
<div v-for="patron in patrons" :key="patron">{{ patron }}</div>
<template #caption>{{ $ts._aboutMisskey.morePatrons }}</template>
<template #caption>{{ i18n.locale._aboutMisskey.morePatrons }}</template>
</FormSection>
</div>
</MkSpacer>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { nextTick, onBeforeUnmount } from 'vue';
import { version } from '@/config';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/key-value.vue';
import MkButton from '@/components/ui/button.vue';
import MkLink from '@/components/link.vue';
import { physics } from '@/scripts/physics';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import * as os from '@/os';
const patrons = [
'まっちゃとーにゅ',
@ -145,58 +151,52 @@ const patrons = [
'蝉暮せせせ',
];
export default defineComponent({
components: {
FormSection,
FormLink,
MkKeyValue,
MkLink,
},
let easterEggReady = false;
let easterEggEmojis = $ref([]);
let easterEggEngine = $ref(null);
const containerEl = $ref<HTMLElement>();
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.aboutMisskey,
icon: null
},
version,
patrons,
easterEggReady: false,
easterEggEmojis: [],
easterEggEngine: null,
}
},
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);
}
function iconLoaded() {
const emojis = defaultStore.state.reactions;
const containerWidth = containerEl.offsetWidth;
for (let i = 0; i < 32; i++) {
easterEggEmojis.push({
id: i.toString(),
top: -(128 + (Math.random() * 256)),
left: (Math.random() * containerWidth),
emoji: emojis[Math.floor(Math.random() * emojis.length)],
});
}
nextTick(() => {
easterEggReady = true;
});
}
function gravity() {
if (!easterEggReady) return;
easterEggReady = false;
easterEggEngine = physics(containerEl);
}
function iLoveMisskey() {
os.post({
initialText: 'I $[jelly ❤] #Misskey',
});
}
onBeforeUnmount(() => {
if (easterEggEngine) {
easterEggEngine.stop();
}
});
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.aboutMisskey,
icon: null,
bg: 'var(--bg)',
},
});
</script>

View File

@ -24,7 +24,7 @@
</FormSection>
<FormSection>
<div class="_inputSplit _formBlock">
<FormSplit>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.administrator }}</template>
<template #value>{{ $instance.maintainerName }}</template>
@ -33,14 +33,14 @@
<template #key>{{ $ts.contact }}</template>
<template #value>{{ $instance.maintainerEmail }}</template>
</MkKeyValue>
</div>
</FormSplit>
<FormLink v-if="$instance.tosUrl" :to="$instance.tosUrl" class="_formBlock" external>{{ $ts.tos }}</FormLink>
</FormSection>
<FormSuspense :p="initStats">
<FormSection>
<template #label>{{ $ts.statistics }}</template>
<div class="_inputSplit">
<FormSplit>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.users }}</template>
<template #value>{{ number(stats.originalUsersCount) }}</template>
@ -49,7 +49,7 @@
<template #key>{{ $ts.notes }}</template>
<template #value>{{ number(stats.originalNotesCount) }}</template>
</MkKeyValue>
</div>
</FormSplit>
</FormSection>
</FormSuspense>
@ -67,46 +67,33 @@
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { ref } from 'vue';
import { version, instanceName } from '@/config';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSplit from '@/components/form/split.vue';
import MkKeyValue from '@/components/key-value.vue';
import * as os from '@/os';
import number from '@/filters/number';
import * as symbols from '@/symbols';
import { host } from '@/config';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
MkKeyValue,
FormSection,
FormLink,
FormSuspense,
const stats = ref(null);
const initStats = () => os.api('stats', {
}).then((res) => {
stats.value = res;
});
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.instanceInfo,
icon: 'fas fa-info-circle',
bg: 'var(--bg)',
},
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>

View File

@ -34,27 +34,7 @@
-->
<MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);">
<div v-for="report in items" :key="report.id" class="bcekxzvu _card _gap">
<div class="_content target">
<MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/>
<div class="info">
<MkUserName class="name" :user="report.targetUser"/>
<div class="acct">@{{ acct(report.targetUser) }}</div>
</div>
</div>
<div class="_content">
<div>
<Mfm :text="report.comment"/>
</div>
<hr>
<div>Reporter: <MkAcct :user="report.reporter"/></div>
<div><MkTime :time="report.createdAt"/></div>
</div>
<div class="_footer">
<div v-if="report.assignee">Assignee: <MkAcct :user="report.assignee"/></div>
<MkButton v-if="!report.resolved" primary @click="resolve(report)">{{ $ts.abuseMarkAsResolved }}</MkButton>
</div>
</div>
<XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/>
</MkPagination>
</div>
</div>
@ -62,22 +42,21 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { computed, defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
import MkPagination from '@/components/ui/pagination.vue';
import { acct } from '@/filters/user';
import XAbuseReport from '@/components/abuse-report.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
MkButton,
MkInput,
MkSelect,
MkPagination,
XAbuseReport,
},
emits: ['info'],
@ -95,44 +74,20 @@ export default defineComponent({
reporterOrigin: 'combined',
targetUserOrigin: 'combined',
pagination: {
endpoint: 'admin/abuse-user-reports',
endpoint: 'admin/abuse-user-reports' as const,
limit: 10,
params: () => ({
params: computed(() => ({
state: this.state,
reporterOrigin: this.reporterOrigin,
targetUserOrigin: this.targetUserOrigin,
}),
})),
},
}
},
watch: {
state() {
this.$refs.reports.reload();
},
reporterOrigin() {
this.$refs.reports.reload();
},
targetUserOrigin() {
this.$refs.reports.reload();
},
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
acct,
resolve(report) {
os.apiWithDialog('admin/resolve-abuse-user-report', {
reportId: report.id,
}).then(() => {
this.$refs.reports.removeItem(item => item.id === report.id);
});
resolved(reportId) {
this.$refs.reports.removeItem(item => item.id === reportId);
},
}
});
@ -142,29 +97,4 @@ export default defineComponent({
.lcixvhis {
margin: var(--margin);
}
.bcekxzvu {
> .target {
display: flex;
width: 100%;
box-sizing: border-box;
text-align: left;
align-items: center;
> .avatar {
width: 42px;
height: 42px;
}
> .info {
margin-left: 0.3em;
padding: 0 8px;
flex: 1;
> .name {
font-weight: bold;
}
}
}
}
</style>

View File

@ -23,14 +23,14 @@
<MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio>
</div>
-->
<div class="_inputSplit">
<FormSplit>
<MkInput v-model="ad.ratio" type="number">
<template #label>{{ $ts.ratio }}</template>
</MkInput>
<MkInput v-model="ad.expiresAt" type="date">
<template #label>{{ $ts.expiration }}</template>
</MkInput>
</div>
</FormSplit>
<MkTextarea v-model="ad.memo" class="_formBlock">
<template #label>{{ $ts.memo }}</template>
</MkTextarea>
@ -49,6 +49,7 @@ import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkTextarea from '@/components/form/textarea.vue';
import FormRadios from '@/components/form/radios.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
@ -58,6 +59,7 @@ export default defineComponent({
MkInput,
MkTextarea,
FormRadios,
FormSplit,
},
emits: ['info'],
@ -85,10 +87,6 @@ export default defineComponent({
});
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
add() {
this.ads.unshift({

View File

@ -61,10 +61,6 @@ export default defineComponent({
});
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
add() {
this.announcements.unshift({

View File

@ -1,70 +1,55 @@
<template>
<FormBase>
<div>
<FormSuspense :p="init">
<FormRadios v-model="provider">
<template #desc><i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}</template>
<option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option>
<option value="hcaptcha">hCaptcha</option>
<option value="recaptcha">reCAPTCHA</option>
</FormRadios>
<div class="_formRoot">
<FormRadios v-model="provider" class="_formBlock">
<option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option>
<option value="hcaptcha">hCaptcha</option>
<option value="recaptcha">reCAPTCHA</option>
</FormRadios>
<template v-if="provider === 'hcaptcha'">
<div v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
<div class="_debobigegoLabel">hCaptcha</div>
<div class="main">
<FormInput v-model="hcaptchaSiteKey">
<template #prefix><i class="fas fa-key"></i></template>
<span>{{ $ts.hcaptchaSiteKey }}</span>
</FormInput>
<FormInput v-model="hcaptchaSecretKey">
<template #prefix><i class="fas fa-key"></i></template>
<span>{{ $ts.hcaptchaSecretKey }}</span>
</FormInput>
</div>
</div>
<div v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
<div class="_debobigegoLabel">{{ $ts.preview }}</div>
<div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);">
<template v-if="provider === 'hcaptcha'">
<FormInput v-model="hcaptchaSiteKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>{{ $ts.hcaptchaSiteKey }}</template>
</FormInput>
<FormInput v-model="hcaptchaSecretKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>{{ $ts.hcaptchaSecretKey }}</template>
</FormInput>
<FormSlot class="_formBlock">
<template #label>{{ $ts.preview }}</template>
<MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
</div>
</div>
</template>
<template v-else-if="provider === 'recaptcha'">
<div v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
<div class="_debobigegoLabel">reCAPTCHA</div>
<div class="main">
<FormInput v-model="recaptchaSiteKey">
<template #prefix><i class="fas fa-key"></i></template>
<span>{{ $ts.recaptchaSiteKey }}</span>
</FormInput>
<FormInput v-model="recaptchaSecretKey">
<template #prefix><i class="fas fa-key"></i></template>
<span>{{ $ts.recaptchaSecretKey }}</span>
</FormInput>
</div>
</div>
<div v-if="recaptchaSiteKey" v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
<div class="_debobigegoLabel">{{ $ts.preview }}</div>
<div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);">
</FormSlot>
</template>
<template v-else-if="provider === 'recaptcha'">
<FormInput v-model="recaptchaSiteKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>{{ $ts.recaptchaSiteKey }}</template>
</FormInput>
<FormInput v-model="recaptchaSecretKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>{{ $ts.recaptchaSecretKey }}</template>
</FormInput>
<FormSlot v-if="recaptchaSiteKey" class="_formBlock">
<template #label>{{ $ts.preview }}</template>
<MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/>
</div>
</div>
</template>
</FormSlot>
</template>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</div>
</FormSuspense>
</FormBase>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import FormRadios from '@/components/debobigego/radios.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormRadios from '@/components/form/radios.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSlot from '@/components/form/slot.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@ -73,11 +58,9 @@ export default defineComponent({
components: {
FormRadios,
FormInput,
FormBase,
FormGroup,
FormButton,
FormInfo,
FormSuspense,
FormSlot,
MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')),
},
@ -99,10 +82,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -1,28 +1,18 @@
<template>
<FormBase>
<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
<FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
<FormGroup v-for="table in database" :key="table[0]">
<template #label>{{ table[0] }}</template>
<FormKeyValueView>
<template #key>Size</template>
<template #value>{{ bytes(table[1].size) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>Records</template>
<template #value>{{ number(table[1].count) }}</template>
</FormKeyValueView>
</FormGroup>
<MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;">
<template #key>{{ table[0] }}</template>
<template #value>{{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs)</template>
</MkKeyValue>
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormSuspense from '@/components/form/suspense.vue';
import MkKeyValue from '@/components/key-value.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import bytes from '@/filters/bytes';
@ -31,10 +21,7 @@ import number from '@/filters/number';
export default defineComponent({
components: {
FormSuspense,
FormKeyValueView,
FormBase,
FormGroup,
FormLink,
MkKeyValue,
},
emits: ['info'],
@ -50,10 +37,6 @@ export default defineComponent({
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
bytes, number,
}

View File

@ -1,50 +1,55 @@
<template>
<FormBase>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormSwitch v-model="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch>
<div class="_formRoot">
<FormSwitch v-model="enableEmail" class="_formBlock">
<template #label>{{ $ts.enableEmail }}</template>
<template #caption>{{ $ts.emailConfigInfo }}</template>
</FormSwitch>
<template v-if="enableEmail">
<FormInput v-model="email" type="email">
<span>{{ $ts.emailAddress }}</span>
</FormInput>
<template v-if="enableEmail">
<FormInput v-model="email" type="email" class="_formBlock">
<template #label>{{ $ts.emailAddress }}</template>
</FormInput>
<div v-sticky-container class="_debobigegoItem _debobigegoNoConcat">
<div class="_debobigegoLabel">{{ $ts.smtpConfig }}</div>
<div class="main">
<FormInput v-model="smtpHost">
<span>{{ $ts.smtpHost }}</span>
</FormInput>
<FormInput v-model="smtpPort" type="number">
<span>{{ $ts.smtpPort }}</span>
</FormInput>
<FormInput v-model="smtpUser">
<span>{{ $ts.smtpUser }}</span>
</FormInput>
<FormInput v-model="smtpPass" type="password">
<span>{{ $ts.smtpPass }}</span>
</FormInput>
<FormInfo>{{ $ts.emptyToDisableSmtpAuth }}</FormInfo>
<FormSwitch v-model="smtpSecure">{{ $ts.smtpSecure }}<template #desc>{{ $ts.smtpSecureInfo }}</template></FormSwitch>
</div>
</div>
<FormButton @click="testEmail">{{ $ts.testEmail }}</FormButton>
</template>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
<FormSection>
<template #label>{{ $ts.smtpConfig }}</template>
<FormSplit :min-width="280">
<FormInput v-model="smtpHost" class="_formBlock">
<template #label>{{ $ts.smtpHost }}</template>
</FormInput>
<FormInput v-model="smtpPort" type="number" class="_formBlock">
<template #label>{{ $ts.smtpPort }}</template>
</FormInput>
</FormSplit>
<FormSplit :min-width="280">
<FormInput v-model="smtpUser" class="_formBlock">
<template #label>{{ $ts.smtpUser }}</template>
</FormInput>
<FormInput v-model="smtpPass" type="password" class="_formBlock">
<template #label>{{ $ts.smtpPass }}</template>
</FormInput>
</FormSplit>
<FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo>
<FormSwitch v-model="smtpSecure" class="_formBlock">
<template #label>{{ $ts.smtpSecure }}</template>
<template #caption>{{ $ts.smtpSecureInfo }}</template>
</FormSwitch>
</FormSection>
</template>
</div>
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSplit from '@/components/form/split.vue';
import FormSection from '@/components/form/section.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@ -53,9 +58,8 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormGroup,
FormButton,
FormSplit,
FormSection,
FormInfo,
FormSuspense,
},
@ -68,6 +72,16 @@ export default defineComponent({
title: this.$ts.emailServer,
icon: 'fas fa-envelope',
bg: 'var(--bg)',
actions: [{
asFullButton: true,
text: this.$ts.testEmail,
handler: this.testEmail,
}, {
asFullButton: true,
icon: 'fas fa-check',
text: this.$ts.save,
handler: this.save,
}],
},
enableEmail: false,
email: null,
@ -79,10 +93,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -95,7 +95,7 @@ export default defineComponent({
});
if (canceled) return;
os.api('admin/emoji/remove', {
os.api('admin/emoji/delete', {
id: this.emoji.id
}).then(() => {
this.$emit('done', {

View File

@ -6,11 +6,22 @@
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.search }}</template>
</MkInput>
<MkPagination ref="emojis" :pagination="pagination">
<MkSwitch v-model="selectMode" style="margin: 8px 0;">
<template #label>Select mode</template>
</MkSwitch>
<div v-if="selectMode" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
<MkButton inline @click="selectAll">Select all</MkButton>
<MkButton inline @click="setCategoryBulk">Set category</MkButton>
<MkButton inline @click="addTagBulk">Add tag</MkButton>
<MkButton inline @click="removeTagBulk">Remove tag</MkButton>
<MkButton inline @click="setTagBulk">Set tag</MkButton>
<MkButton inline danger @click="delBulk">Delete</MkButton>
</div>
<MkPagination ref="emojisPaginationComponent" :pagination="pagination">
<template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
<template v-slot="{items}">
<div class="ldhfsamy">
<button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)">
<button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)">
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<div class="name _monospace">{{ emoji.name }}</div>
@ -23,7 +34,7 @@
</div>
<div v-else-if="tab === 'remote'" class="remote">
<div class="_inputSplit">
<FormSplit>
<MkInput v-model="queryRemote" :debounce="true" type="search">
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.search }}</template>
@ -31,8 +42,8 @@
<MkInput v-model="host" :debounce="true">
<template #label>{{ $ts.host }}</template>
</MkInput>
</div>
<MkPagination ref="remoteEmojis" :pagination="remotePagination">
</FormSplit>
<MkPagination :pagination="remotePagination">
<template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
<template v-slot="{items}">
<div class="ldhfsamy">
@ -51,146 +62,233 @@
</MkSpacer>
</template>
<script lang="ts">
import { computed, defineComponent, toRef } from 'vue';
<script lang="ts" setup>
import { computed, defineComponent, ref, toRef } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkPagination from '@/components/ui/pagination.vue';
import MkTab from '@/components/tab.vue';
import { selectFiles } from '@/scripts/select-file';
import MkSwitch from '@/components/form/switch.vue';
import FormSplit from '@/components/form/split.vue';
import { selectFile, selectFiles } from '@/scripts/select-file';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
MkTab,
MkButton,
MkInput,
MkPagination,
},
const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>();
emits: ['info'],
const tab = ref('local');
const query = ref(null);
const queryRemote = ref(null);
const host = ref(null);
const selectMode = ref(false);
const selectedEmojis = ref<string[]>([]);
data() {
return {
[symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.customEmojis,
icon: 'fas fa-laugh',
bg: 'var(--bg)',
actions: [{
asFullButton: true,
icon: 'fas fa-plus',
text: this.$ts.addEmoji,
handler: this.add,
}, {
icon: 'fas fa-ellipsis-h',
handler: this.menu,
}],
tabs: [{
active: this.tab === 'local',
title: this.$ts.local,
onClick: () => { this.tab = 'local'; },
}, {
active: this.tab === 'remote',
title: this.$ts.remote,
onClick: () => { this.tab = 'remote'; },
},]
})),
tab: 'local',
query: null,
queryRemote: null,
host: '',
pagination: {
endpoint: 'admin/emoji/list',
limit: 30,
params: computed(() => ({
query: (this.query && this.query !== '') ? this.query : null
}))
},
remotePagination: {
endpoint: 'admin/emoji/list-remote',
limit: 30,
params: computed(() => ({
query: (this.queryRemote && this.queryRemote !== '') ? this.queryRemote : null,
host: (this.host && this.host !== '') ? this.host : null
}))
},
}
},
const pagination = {
endpoint: 'admin/emoji/list' as const,
limit: 30,
params: computed(() => ({
query: (query.value && query.value !== '') ? query.value : null,
})),
};
async mounted() {
this.$emit('info', toRef(this, symbols.PAGE_INFO));
},
const remotePagination = {
endpoint: 'admin/emoji/list-remote' as const,
limit: 30,
params: computed(() => ({
query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null,
host: (host.value && host.value !== '') ? host.value : null,
})),
};
methods: {
async add(e) {
const files = await selectFiles(e.currentTarget || e.target, null);
const 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);
},
menu(ev) {
os.popupMenu([{
icon: 'fas fa-download',
text: this.$ts.export,
action: async () => {
os.api('export-custom-emojis', {
})
.then(() => {
os.alert({
type: 'info',
text: this.$ts.exportRequested,
});
}).catch((e) => {
os.alert({
type: 'error',
text: e.message,
});
});
}
}], ev.currentTarget || ev.target);
}
const selectAll = () => {
if (selectedEmojis.value.length > 0) {
selectedEmojis.value = [];
} else {
selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id);
}
};
const toggleSelect = (emoji) => {
if (selectedEmojis.value.includes(emoji.id)) {
selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id);
} else {
selectedEmojis.value.push(emoji.id);
}
};
const add = async (ev: MouseEvent) => {
const files = await selectFiles(ev.currentTarget || ev.target, null);
const promise = Promise.all(files.map(file => os.api('admin/emoji/add', {
fileId: file.id,
})));
promise.then(() => {
emojisPaginationComponent.value.reload();
});
os.promiseDialog(promise);
};
const edit = (emoji) => {
os.popup(import('./emoji-edit-dialog.vue'), {
emoji: emoji
}, {
done: result => {
if (result.updated) {
emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, {
...emoji,
...result.updated
});
} else if (result.deleted) {
emojisPaginationComponent.value.removeItem(item => item.id === emoji.id);
}
},
}, 'closed');
};
const im = (emoji) => {
os.apiWithDialog('admin/emoji/copy', {
emojiId: emoji.id,
});
};
const remoteMenu = (emoji, ev: MouseEvent) => {
os.popupMenu([{
type: 'label',
text: ':' + emoji.name + ':',
}, {
text: i18n.locale.import,
icon: 'fas fa-plus',
action: () => { im(emoji) }
}], ev.currentTarget || ev.target);
};
const menu = (ev: MouseEvent) => {
os.popupMenu([{
icon: 'fas fa-download',
text: i18n.locale.export,
action: async () => {
os.api('export-custom-emojis', {
})
.then(() => {
os.alert({
type: 'info',
text: i18n.locale.exportRequested,
});
}).catch((e) => {
os.alert({
type: 'error',
text: e.message,
});
});
}
}, {
icon: 'fas fa-upload',
text: i18n.locale.import,
action: async () => {
const file = await selectFile(ev.currentTarget || ev.target);
os.api('admin/emoji/import-zip', {
fileId: file.id,
})
.then(() => {
os.alert({
type: 'info',
text: i18n.locale.importRequested,
});
}).catch((e) => {
os.alert({
type: 'error',
text: e.message,
});
});
}
}], ev.currentTarget || ev.target);
};
const setCategoryBulk = async () => {
const { canceled, result } = await os.inputText({
title: 'Category',
});
if (canceled) return;
await os.apiWithDialog('admin/emoji/set-category-bulk', {
ids: selectedEmojis.value,
category: result,
});
emojisPaginationComponent.value.reload();
};
const addTagBulk = async () => {
const { canceled, result } = await os.inputText({
title: 'Tag',
});
if (canceled) return;
await os.apiWithDialog('admin/emoji/add-aliases-bulk', {
ids: selectedEmojis.value,
aliases: result.split(' '),
});
emojisPaginationComponent.value.reload();
};
const removeTagBulk = async () => {
const { canceled, result } = await os.inputText({
title: 'Tag',
});
if (canceled) return;
await os.apiWithDialog('admin/emoji/remove-aliases-bulk', {
ids: selectedEmojis.value,
aliases: result.split(' '),
});
emojisPaginationComponent.value.reload();
};
const setTagBulk = async () => {
const { canceled, result } = await os.inputText({
title: 'Tag',
});
if (canceled) return;
await os.apiWithDialog('admin/emoji/set-aliases-bulk', {
ids: selectedEmojis.value,
aliases: result.split(' '),
});
emojisPaginationComponent.value.reload();
};
const delBulk = async () => {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.locale.deleteConfirm,
});
if (canceled) return;
await os.apiWithDialog('admin/emoji/delete-bulk', {
ids: selectedEmojis.value,
});
emojisPaginationComponent.value.reload();
};
defineExpose({
[symbols.PAGE_INFO]: computed(() => ({
title: i18n.locale.customEmojis,
icon: 'fas fa-laugh',
bg: 'var(--bg)',
actions: [{
asFullButton: true,
icon: 'fas fa-plus',
text: i18n.locale.addEmoji,
handler: add,
}, {
icon: 'fas fa-ellipsis-h',
handler: menu,
}],
tabs: [{
active: tab.value === 'local',
title: i18n.locale.local,
onClick: () => { tab.value = 'local'; },
}, {
active: tab.value === 'remote',
title: i18n.locale.remote,
onClick: () => { tab.value = 'remote'; },
},]
})),
});
</script>
@ -210,11 +308,16 @@ export default defineComponent({
> .emoji {
display: flex;
align-items: center;
padding: 12px;
padding: 11px;
text-align: left;
border: solid 1px var(--panel);
&:hover {
color: var(--accent);
border-color: var(--inputBorderHover);
}
&.selected {
border-color: var(--accent);
}
> .img {

View File

@ -1,93 +0,0 @@
<template>
<FormBase>
<FormSuspense :p="init">
<FormSwitch v-model="cacheRemoteFiles">
{{ $ts.cacheRemoteFiles }}
<template #desc>{{ $ts.cacheRemoteFilesDescription }}</template>
</FormSwitch>
<FormSwitch v-model="proxyRemoteFiles">
{{ $ts.proxyRemoteFiles }}
<template #desc>{{ $ts.proxyRemoteFilesDescription }}</template>
</FormSwitch>
<FormInput v-model="localDriveCapacityMb" type="number">
<span>{{ $ts.driveCapacityPerLocalAccount }}</span>
<template #suffix>MB</template>
<template #desc>{{ $ts.inMb }}</template>
</FormInput>
<FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles">
<span>{{ $ts.driveCapacityPerRemoteAccount }}</span>
<template #suffix>MB</template>
<template #desc>{{ $ts.inMb }}</template>
</FormInput>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormSuspense>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormGroup,
FormButton,
FormSuspense,
},
emits: ['info'],
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.files,
icon: 'fas fa-cloud',
bg: 'var(--bg)',
},
cacheRemoteFiles: false,
proxyRemoteFiles: false,
localDriveCapacityMb: 0,
remoteDriveCapacityMb: 0,
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
this.cacheRemoteFiles = meta.cacheRemoteFiles;
this.proxyRemoteFiles = meta.proxyRemoteFiles;
this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
},
save() {
os.apiWithDialog('admin/update-meta', {
cacheRemoteFiles: this.cacheRemoteFiles,
proxyRemoteFiles: this.proxyRemoteFiles,
localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
}).then(() => {
fetchInstance();
});
}
}
});
</script>

View File

@ -19,7 +19,7 @@
<option value="local">{{ $ts.local }}</option>
<option value="remote">{{ $ts.remote }}</option>
</MkSelect>
<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params().origin === 'local'">
<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'">
<template #label>{{ $ts.host }}</template>
</MkInput>
</div>
@ -55,7 +55,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { computed, defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
@ -95,33 +95,17 @@ export default defineComponent({
type: null,
searchHost: '',
pagination: {
endpoint: 'admin/drive/files',
endpoint: 'admin/drive/files' as const,
limit: 10,
params: () => ({
params: computed(() => ({
type: (this.type && this.type !== '') ? this.type : null,
origin: this.origin,
hostname: (this.hostname && this.hostname !== '') ? this.hostname : null,
}),
hostname: (this.searchHost && this.searchHost !== '') ? this.searchHost : null,
})),
},
}
},
watch: {
type() {
this.$refs.files.reload();
},
origin() {
this.$refs.files.reload();
},
searchHost() {
this.$refs.files.reload();
},
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
clear() {
os.confirm({

View File

@ -3,14 +3,14 @@
<div v-if="!narrow || page == null" class="nav">
<MkHeader :info="header"></MkHeader>
<MkSpacer :content-max="700">
<MkSpacer :content-max="700" :margin-min="16">
<div class="lxpfedzu">
<div class="banner">
<img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/>
</div>
<MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/bot-protection" class="_link">{{ $ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo>
<MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu>
</div>
@ -19,7 +19,7 @@
<div class="main">
<MkStickyContainer>
<template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template>
<component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/>
<component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/>
</MkStickyContainer>
</div>
</div>
@ -29,9 +29,6 @@
import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { i18n } from '@/i18n';
import MkSuperMenu from '@/components/ui/super-menu.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormButton from '@/components/debobigego/button.vue';
import MkInfo from '@/components/ui/info.vue';
import { scroll } from '@/scripts/scroll';
import { instance } from '@/instance';
@ -41,10 +38,7 @@ import { lookupUser } from '@/scripts/lookup-user';
export default defineComponent({
components: {
FormBase,
MkSuperMenu,
FormGroup,
FormButton,
MkInfo,
},
@ -72,7 +66,9 @@ export default defineComponent({
const narrow = ref(false);
const view = ref(null);
const el = ref(null);
const onInfo = (viewInfo) => {
const pageChanged = (page) => {
if (page == null) return;
const viewInfo = page[symbols.PAGE_INFO];
if (isRef(viewInfo)) {
watch(viewInfo, () => {
childInfo.value = viewInfo.value;
@ -162,11 +158,6 @@ export default defineComponent({
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,
@ -182,11 +173,6 @@ export default defineComponent({
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,
@ -236,17 +222,11 @@ export default defineComponent({
case 'database': return defineAsyncComponent(() => import('./database.vue'));
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue'));
case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue'));
case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue'));
case 'security': return defineAsyncComponent(() => import('./security.vue'));
case 'bot-protection': return defineAsyncComponent(() => import('./bot-protection.vue'));
case 'service-worker': return defineAsyncComponent(() => import('./service-worker.vue'));
case 'relays': return defineAsyncComponent(() => import('./relays.vue'));
case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
case 'integrations/twitter': return defineAsyncComponent(() => import('./integrations-twitter.vue'));
case 'integrations/github': return defineAsyncComponent(() => import('./integrations-github.vue'));
case 'integrations/discord': return defineAsyncComponent(() => import('./integrations-discord.vue'));
case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue'));
@ -333,7 +313,7 @@ export default defineComponent({
narrow,
view,
el,
onInfo,
pageChanged,
childInfo,
pageProps,
component,

View File

@ -1,39 +1,29 @@
<template>
<FormBase>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormTextarea v-model="blockedHosts">
<FormTextarea v-model="blockedHosts" class="_formBlock">
<span>{{ $ts.blockedInstances }}</span>
<template #desc>{{ $ts.blockedInstancesDescription }}</template>
<template #caption>{{ $ts.blockedInstancesDescription }}</template>
</FormTextarea>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormTextarea from '@/components/debobigego/textarea.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormButton from '@/components/ui/button.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormGroup,
FormButton,
FormTextarea,
FormInfo,
FormSuspense,
},
@ -50,10 +40,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -1,291 +0,0 @@
<template>
<XModalWindow ref="dialog"
:width="520"
:height="500"
@close="$refs.dialog.close()"
@closed="$emit('closed')"
>
<template #header>{{ instance.host }}</template>
<div class="mk-instance-info">
<div class="_table section">
<div class="_row">
<div class="_cell">
<div class="_label">{{ $ts.software }}</div>
<div class="_data">{{ instance.softwareName || '?' }}</div>
</div>
<div class="_cell">
<div class="_label">{{ $ts.version }}</div>
<div class="_data">{{ instance.softwareVersion || '?' }}</div>
</div>
</div>
</div>
<div class="_table data section">
<div class="_row">
<div class="_cell">
<div class="_label">{{ $ts.registeredAt }}</div>
<div class="_data">{{ new Date(instance.caughtAt).toLocaleString() }} (<MkTime :time="instance.caughtAt"/>)</div>
</div>
</div>
<div class="_row">
<div class="_cell">
<div class="_label">{{ $ts.following }}</div>
<button class="_data _textButton" @click="showFollowing()">{{ number(instance.followingCount) }}</button>
</div>
<div class="_cell">
<div class="_label">{{ $ts.followers }}</div>
<button class="_data _textButton" @click="showFollowers()">{{ number(instance.followersCount) }}</button>
</div>
</div>
<div class="_row">
<div class="_cell">
<div class="_label">{{ $ts.users }}</div>
<button class="_data _textButton" @click="showUsers()">{{ number(instance.usersCount) }}</button>
</div>
<div class="_cell">
<div class="_label">{{ $ts.notes }}</div>
<div class="_data">{{ number(instance.notesCount) }}</div>
</div>
</div>
<div class="_row">
<div class="_cell">
<div class="_label">{{ $ts.files }}</div>
<div class="_data">{{ number(instance.driveFiles) }}</div>
</div>
<div class="_cell">
<div class="_label">{{ $ts.storageUsage }}</div>
<div class="_data">{{ bytes(instance.driveUsage) }}</div>
</div>
</div>
<div class="_row">
<div class="_cell">
<div class="_label">{{ $ts.latestRequestSentAt }}</div>
<div class="_data"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div>
</div>
<div class="_cell">
<div class="_label">{{ $ts.latestStatus }}</div>
<div class="_data">{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</div>
</div>
</div>
<div class="_row">
<div class="_cell">
<div class="_label">{{ $ts.latestRequestReceivedAt }}</div>
<div class="_data"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div>
</div>
</div>
</div>
<div class="chart">
<div class="header">
<span class="label">{{ $ts.charts }}</span>
<div class="selects">
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
<option value="instance-requests">{{ $ts._instanceCharts.requests }}</option>
<option value="instance-users">{{ $ts._instanceCharts.users }}</option>
<option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option>
<option value="instance-notes">{{ $ts._instanceCharts.notes }}</option>
<option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option>
<option value="instance-ff">{{ $ts._instanceCharts.ff }}</option>
<option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option>
<option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option>
<option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option>
<option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option>
<option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option>
</MkSelect>
<MkSelect v-model="chartSpan" style="margin: 0;">
<option value="hour">{{ $ts.perHour }}</option>
<option value="day">{{ $ts.perDay }}</option>
</MkSelect>
</div>
</div>
<div class="chart">
<MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart>
</div>
</div>
<div class="operations section">
<span class="label">{{ $ts.operations }}</span>
<MkSwitch v-model="isSuspended" class="switch">{{ $ts.stopActivityDelivery }}</MkSwitch>
<MkSwitch :model-value="isBlocked" class="switch" @update:modelValue="changeBlock">{{ $ts.blockThisInstance }}</MkSwitch>
<details>
<summary>{{ $ts.deleteAllFiles }}</summary>
<MkButton style="margin: 0.5em 0 0.5em 0;" @click="deleteAllFiles()"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton>
</details>
<details>
<summary>{{ $ts.removeAllFollowing }}</summary>
<MkButton style="margin: 0.5em 0 0.5em 0;" @click="removeAllFollowing()"><i class="fas fa-minus-circle"></i> {{ $ts.removeAllFollowing }}</MkButton>
<MkInfo warn>{{ $t('removeAllFollowingDescription', { host: instance.host }) }}</MkInfo>
</details>
</div>
<details class="metadata section">
<summary class="label">{{ $ts.metadata }}</summary>
<pre><code>{{ JSON.stringify(instance, null, 2) }}</code></pre>
</details>
</div>
</XModalWindow>
</template>
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import XModalWindow from '@/components/ui/modal-window.vue';
import MkSelect from '@/components/form/select.vue';
import MkButton from '@/components/ui/button.vue';
import MkSwitch from '@/components/form/switch.vue';
import MkInfo from '@/components/ui/info.vue';
import MkChart from '@/components/chart.vue';
import bytes from '@/filters/bytes';
import number from '@/filters/number';
import * as os from '@/os';
export default defineComponent({
components: {
XModalWindow,
MkSelect,
MkButton,
MkSwitch,
MkInfo,
MkChart,
},
props: {
instance: {
type: Object,
required: true
}
},
emits: ['closed'],
data() {
return {
isSuspended: this.instance.isSuspended,
chartSrc: 'requests',
chartSpan: 'hour',
};
},
computed: {
meta() {
return this.$instance;
},
isBlocked() {
return this.meta && this.meta.blockedHosts && this.meta.blockedHosts.includes(this.instance.host);
}
},
watch: {
isSuspended() {
os.api('admin/federation/update-instance', {
host: this.instance.host,
isSuspended: this.isSuspended
});
},
},
methods: {
changeBlock(e) {
os.api('admin/update-meta', {
blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host)
});
},
removeAllFollowing() {
os.apiWithDialog('admin/federation/remove-all-following', {
host: this.instance.host
});
},
deleteAllFiles() {
os.apiWithDialog('admin/federation/delete-all-files', {
host: this.instance.host
});
},
showFollowing() {
// TODO: ページ遷移
},
showFollowers() {
// TODO: ページ遷移
},
showUsers() {
// TODO: ページ遷移
},
bytes,
number
}
});
</script>
<style lang="scss" scoped>
.mk-instance-info {
overflow: auto;
> .section {
padding: 16px 32px;
@media (max-width: 500px) {
padding: 8px 16px;
}
&:not(:first-child) {
border-top: solid 0.5px var(--divider);
}
}
> .chart {
border-top: solid 0.5px var(--divider);
padding: 16px 0 12px 0;
> .header {
padding: 0 32px;
@media (max-width: 500px) {
padding: 0 16px;
}
> .label {
font-size: 80%;
opacity: 0.7;
}
> .selects {
display: flex;
}
}
> .chart {
padding: 0 16px;
@media (max-width: 500px) {
padding: 0;
}
}
}
> .operations {
> .label {
font-size: 80%;
opacity: 0.7;
}
> .switch {
margin: 16px 0;
}
}
> .metadata {
> .label {
font-size: 80%;
opacity: 0.7;
}
> pre > code {
display: block;
max-height: 200px;
overflow: auto;
}
}
}
</style>

View File

@ -1,37 +1,36 @@
<template>
<FormBase>
<FormSuspense :p="init">
<FormSwitch v-model="enableDiscordIntegration">
{{ $ts.enable }}
<FormSuspense :p="init">
<div class="_formRoot">
<FormSwitch v-model="enableDiscordIntegration" class="_formBlock">
<template #label>{{ $ts.enable }}</template>
</FormSwitch>
<template v-if="enableDiscordIntegration">
<FormInfo>Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo>
<FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo>
<FormInput v-model="discordClientId">
<FormInput v-model="discordClientId" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
Client ID
<template #label>Client ID</template>
</FormInput>
<FormInput v-model="discordClientSecret">
<FormInput v-model="discordClientSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
Client Secret
<template #label>Client Secret</template>
</FormInput>
</template>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormSuspense>
</FormBase>
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</div>
</FormSuspense>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@ -40,7 +39,6 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormInfo,
FormButton,
FormSuspense,
@ -60,10 +58,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -1,37 +1,36 @@
<template>
<FormBase>
<FormSuspense :p="init">
<FormSwitch v-model="enableGithubIntegration">
{{ $ts.enable }}
<FormSuspense :p="init">
<div class="_formRoot">
<FormSwitch v-model="enableGithubIntegration" class="_formBlock">
<template #label>{{ $ts.enable }}</template>
</FormSwitch>
<template v-if="enableGithubIntegration">
<FormInfo>Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo>
<FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo>
<FormInput v-model="githubClientId">
<FormInput v-model="githubClientId" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
Client ID
<template #label>Client ID</template>
</FormInput>
<FormInput v-model="githubClientSecret">
<FormInput v-model="githubClientSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
Client Secret
<template #label>Client Secret</template>
</FormInput>
</template>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormSuspense>
</FormBase>
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</div>
</FormSuspense>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@ -40,7 +39,6 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormInfo,
FormButton,
FormSuspense,
@ -60,10 +58,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -1,37 +1,36 @@
<template>
<FormBase>
<FormSuspense :p="init">
<FormSwitch v-model="enableTwitterIntegration">
{{ $ts.enable }}
<FormSuspense :p="init">
<div class="_formRoot">
<FormSwitch v-model="enableTwitterIntegration" class="_formBlock">
<template #label>{{ $ts.enable }}</template>
</FormSwitch>
<template v-if="enableTwitterIntegration">
<FormInfo>Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo>
<FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo>
<FormInput v-model="twitterConsumerKey">
<FormInput v-model="twitterConsumerKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
Consumer Key
<template #label>Consumer Key</template>
</FormInput>
<FormInput v-model="twitterConsumerSecret">
<FormInput v-model="twitterConsumerSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
Consumer Secret
<template #label>Consumer Secret</template>
</FormInput>
</template>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormSuspense>
</FormBase>
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</div>
</FormSuspense>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@ -40,7 +39,6 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormInfo,
FormButton,
FormSuspense,
@ -60,10 +58,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -1,46 +1,48 @@
<template>
<FormBase>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormLink to="/admin/integrations/twitter">
<i class="fab fa-twitter"></i> Twitter
<FormFolder class="_formBlock">
<template #icon><i class="fab fa-twitter"></i></template>
<template #label>Twitter</template>
<template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template>
</FormLink>
<FormLink to="/admin/integrations/github">
<i class="fab fa-github"></i> GitHub
<XTwitter/>
</FormFolder>
<FormFolder to="/admin/integrations/github" class="_formBlock">
<template #icon><i class="fab fa-github"></i></template>
<template #label>GitHub</template>
<template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template>
</FormLink>
<FormLink to="/admin/integrations/discord">
<i class="fab fa-discord"></i> Discord
<XGithub/>
</FormFolder>
<FormFolder to="/admin/integrations/discord" class="_formBlock">
<template #icon><i class="fab fa-discord"></i></template>
<template #label>Discord</template>
<template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template>
</FormLink>
<XDiscord/>
</FormFolder>
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormLink from '@/components/debobigego/link.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormTextarea from '@/components/debobigego/textarea.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormFolder from '@/components/form/folder.vue';
import FormSecion from '@/components/form/section.vue';
import FormSuspense from '@/components/form/suspense.vue';
import XTwitter from './integrations.twitter.vue';
import XGithub from './integrations.github.vue';
import XDiscord from './integrations.discord.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
FormLink,
FormInput,
FormBase,
FormGroup,
FormButton,
FormTextarea,
FormInfo,
FormFolder,
FormSecion,
FormSuspense,
XTwitter,
XGithub,
XDiscord,
},
emits: ['info'],
@ -58,10 +60,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -76,7 +76,6 @@ import MkwFederation from '../../widgets/federation.vue';
import { version, url } from '@/config';
import bytes from '@/filters/bytes';
import number from '@/filters/number';
import MkInstanceInfo from './instance.vue';
Chart.register(
ArcElement,
@ -101,6 +100,7 @@ const alpha = (hex, a) => {
return `rgba(${r}, ${g}, ${b}, ${a})`;
};
import * as os from '@/os';
import { stream } from '@/stream';
export default defineComponent({
components: {
@ -119,7 +119,7 @@ export default defineComponent({
stats: null,
serverInfo: null,
connection: null,
queueConnection: markRaw(os.stream.useChannel('queueStats')),
queueConnection: markRaw(stream.useChannel('queueStats')),
memUsage: 0,
chartCpuMem: null,
chartNet: null,
@ -150,7 +150,7 @@ export default defineComponent({
os.api('admin/server-info', {}).then(res => {
this.serverInfo = res;
this.connection = markRaw(os.stream.useChannel('serverStats'));
this.connection = markRaw(stream.useChannel('serverStats'));
this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send('requestLog', {

View File

@ -1,76 +1,78 @@
<template>
<FormBase>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormSwitch v-model="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch>
<div class="_formRoot">
<FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch>
<template v-if="useObjectStorage">
<FormInput v-model="objectStorageBaseUrl">
<span>{{ $ts.objectStorageBaseUrl }}</span>
<template #desc>{{ $ts.objectStorageBaseUrlDesc }}</template>
</FormInput>
<template v-if="useObjectStorage">
<FormInput v-model="objectStorageBaseUrl" class="_formBlock">
<template #label>{{ $ts.objectStorageBaseUrl }}</template>
<template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template>
</FormInput>
<FormInput v-model="objectStorageBucket">
<span>{{ $ts.objectStorageBucket }}</span>
<template #desc>{{ $ts.objectStorageBucketDesc }}</template>
</FormInput>
<FormInput v-model="objectStorageBucket" class="_formBlock">
<template #label>{{ $ts.objectStorageBucket }}</template>
<template #caption>{{ $ts.objectStorageBucketDesc }}</template>
</FormInput>
<FormInput v-model="objectStoragePrefix">
<span>{{ $ts.objectStoragePrefix }}</span>
<template #desc>{{ $ts.objectStoragePrefixDesc }}</template>
</FormInput>
<FormInput v-model="objectStoragePrefix" class="_formBlock">
<template #label>{{ $ts.objectStoragePrefix }}</template>
<template #caption>{{ $ts.objectStoragePrefixDesc }}</template>
</FormInput>
<FormInput v-model="objectStorageEndpoint">
<span>{{ $ts.objectStorageEndpoint }}</span>
<template #desc>{{ $ts.objectStorageEndpointDesc }}</template>
</FormInput>
<FormInput v-model="objectStorageEndpoint" class="_formBlock">
<template #label>{{ $ts.objectStorageEndpoint }}</template>
<template #caption>{{ $ts.objectStorageEndpointDesc }}</template>
</FormInput>
<FormInput v-model="objectStorageRegion">
<span>{{ $ts.objectStorageRegion }}</span>
<template #desc>{{ $ts.objectStorageRegionDesc }}</template>
</FormInput>
<FormInput v-model="objectStorageRegion" class="_formBlock">
<template #label>{{ $ts.objectStorageRegion }}</template>
<template #caption>{{ $ts.objectStorageRegionDesc }}</template>
</FormInput>
<FormInput v-model="objectStorageAccessKey">
<template #prefix><i class="fas fa-key"></i></template>
<span>Access key</span>
</FormInput>
<FormSplit :min-width="280">
<FormInput v-model="objectStorageAccessKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Access key</template>
</FormInput>
<FormInput v-model="objectStorageSecretKey">
<template #prefix><i class="fas fa-key"></i></template>
<span>Secret key</span>
</FormInput>
<FormInput v-model="objectStorageSecretKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Secret key</template>
</FormInput>
</FormSplit>
<FormSwitch v-model="objectStorageUseSSL">
{{ $ts.objectStorageUseSSL }}
<template #desc>{{ $ts.objectStorageUseSSLDesc }}</template>
</FormSwitch>
<FormSwitch v-model="objectStorageUseSSL" class="_formBlock">
<template #label>{{ $ts.objectStorageUseSSL }}</template>
<template #caption>{{ $ts.objectStorageUseSSLDesc }}</template>
</FormSwitch>
<FormSwitch v-model="objectStorageUseProxy">
{{ $ts.objectStorageUseProxy }}
<template #desc>{{ $ts.objectStorageUseProxyDesc }}</template>
</FormSwitch>
<FormSwitch v-model="objectStorageUseProxy" class="_formBlock">
<template #label>{{ $ts.objectStorageUseProxy }}</template>
<template #caption>{{ $ts.objectStorageUseProxyDesc }}</template>
</FormSwitch>
<FormSwitch v-model="objectStorageSetPublicRead">
{{ $ts.objectStorageSetPublicRead }}
</FormSwitch>
<FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock">
<template #label>{{ $ts.objectStorageSetPublicRead }}</template>
</FormSwitch>
<FormSwitch v-model="objectStorageS3ForcePathStyle">
s3ForcePathStyle
</FormSwitch>
</template>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
<FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock">
<template #label>s3ForcePathStyle</template>
</FormSwitch>
</template>
</div>
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormGroup from '@/components/form/group.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSplit from '@/components/form/split.vue';
import FormSection from '@/components/form/section.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@ -79,10 +81,10 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormGroup,
FormButton,
FormSuspense,
FormSplit,
FormSection,
},
emits: ['info'],
@ -93,6 +95,12 @@ export default defineComponent({
title: this.$ts.objectStorage,
icon: 'fas fa-cloud',
bg: 'var(--bg)',
actions: [{
asFullButton: true,
icon: 'fas fa-check',
text: this.$ts.save,
handler: this.save,
}],
},
useObjectStorage: false,
objectStorageBaseUrl: null,
@ -110,10 +118,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -1,34 +1,17 @@
<template>
<FormBase>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormGroup>
<FormInput v-model="summalyProxy">
<template #prefix><i class="fas fa-link"></i></template>
Summaly Proxy URL
</FormInput>
</FormGroup>
<FormGroup>
<FormInput v-model="deeplAuthKey">
<template #prefix><i class="fas fa-key"></i></template>
DeepL Auth Key
</FormInput>
<FormSwitch v-model="deeplIsPro">
Pro account
</FormSwitch>
</FormGroup>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
none
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormSection from '@/components/form/section.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@ -37,9 +20,7 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormGroup,
FormButton,
FormSection,
FormSuspense,
},
@ -51,29 +32,22 @@ export default defineComponent({
title: this.$ts.other,
icon: 'fas fa-cogs',
bg: 'var(--bg)',
actions: [{
asFullButton: true,
icon: 'fas fa-check',
text: this.$ts.save,
handler: this.save,
}],
},
summalyProxy: '',
deeplAuthKey: '',
deeplIsPro: false,
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
this.summalyProxy = meta.summalyProxy;
this.deeplAuthKey = meta.deeplAuthKey;
this.deeplIsPro = meta.deeplIsPro;
},
save() {
os.apiWithDialog('admin/update-meta', {
summalyProxy: this.summalyProxy,
deeplAuthKey: this.deeplAuthKey,
deeplIsPro: this.deeplIsPro,
}).then(() => {
fetchInstance();
});

View File

@ -19,7 +19,7 @@
<MkContainer :foldable="true" class="charts">
<template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template>
<div style="padding-top: 12px;">
<div style="padding: 12px;">
<MkInstanceStats :chart-limit="500" :detailed="true"/>
</div>
</MkContainer>
@ -67,7 +67,6 @@
<script lang="ts">
import { computed, defineComponent, markRaw, version as vueVersion } from 'vue';
import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
import MkInstanceStats from '@/components/instance-stats.vue';
import MkButton from '@/components/ui/button.vue';
import MkSelect from '@/components/form/select.vue';
@ -78,15 +77,14 @@ import MkQueueChart from '@/components/queue-chart.vue';
import { version, url } from '@/config';
import bytes from '@/filters/bytes';
import number from '@/filters/number';
import MkInstanceInfo from './instance.vue';
import XMetrics from './metrics.vue';
import * as os from '@/os';
import { stream } from '@/stream';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
MkNumberDiff,
FormKeyValueView,
MkInstanceStats,
MkContainer,
MkFolder,
@ -113,13 +111,11 @@ export default defineComponent({
notesComparedToThePrevDay: null,
fetchJobs: () => os.api('admin/queue/deliver-delayed', {}),
fetchModLogs: () => os.api('admin/show-moderation-logs', {}),
queueStatsConnection: markRaw(os.stream.useChannel('queueStats')),
queueStatsConnection: markRaw(stream.useChannel('queueStats')),
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
os.api('meta', { detail: true }).then(meta => {
this.meta = meta;
});
@ -160,9 +156,7 @@ export default defineComponent({
host: q
});
}
os.popup(MkInstanceInfo, {
instance: instance
}, {}, 'closed');
// TODO
},
bytes,

View File

@ -1,42 +1,32 @@
<template>
<FormBase>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormGroup>
<FormKeyValueView>
<template #key>{{ $ts.proxyAccount }}</template>
<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template>
</FormKeyValueView>
<template #caption>{{ $ts.proxyAccountDescription }}</template>
</FormGroup>
<MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.proxyAccount }}</template>
<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template>
</MkKeyValue>
<FormButton primary @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton>
<FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton>
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormTextarea from '@/components/debobigego/textarea.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import MkKeyValue from '@/components/key-value.vue';
import FormButton from '@/components/ui/button.vue';
import MkInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
FormKeyValueView,
FormInput,
FormBase,
FormGroup,
MkKeyValue,
FormButton,
FormTextarea,
FormInfo,
MkInfo,
FormSuspense,
},
@ -54,10 +44,6 @@ export default defineComponent({
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });

View File

@ -1,28 +1,25 @@
<template>
<FormBase>
<MkSpacer :content-max="800">
<XQueue :connection="connection" domain="inbox">
<template #title>In</template>
</XQueue>
<XQueue :connection="connection" domain="deliver">
<template #title>Out</template>
</XQueue>
<FormButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</FormButton>
</FormBase>
<MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import MkButton from '@/components/ui/button.vue';
import XQueue from './queue.chart.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormButton from '@/components/debobigego/button.vue';
import * as os from '@/os';
import { stream } from '@/stream';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormButton,
MkButton,
XQueue,
},
@ -36,13 +33,11 @@ export default defineComponent({
icon: 'fas fa-clipboard-list',
bg: 'var(--bg)',
},
connection: markRaw(os.stream.useChannel('queueStats')),
connection: markRaw(stream.useChannel('queueStats')),
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
this.$nextTick(() => {
this.connection.send('requestLog', {
id: Math.random().toString().substr(2, 8),

View File

@ -1,32 +1,27 @@
<template>
<FormBase class="relaycxt">
<FormButton primary @click="addRelay"><i class="fas fa-plus"></i> {{ $ts.addRelay }}</FormButton>
<div v-for="relay in relays" :key="relay.inbox" class="_debobigegoItem">
<div class="_debobigegoPanel" style="padding: 16px;">
<div>{{ relay.inbox }}</div>
<div>{{ $t(`_relayStatus.${relay.status}`) }}</div>
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
<MkSpacer :content-max="800">
<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel _block" style="padding: 16px;">
<div>{{ relay.inbox }}</div>
<div class="status">
<i v-if="relay.status === 'accepted'" class="fas fa-check icon accepted"></i>
<i v-else-if="relay.status === 'rejected'" class="fas fa-ban icon rejected"></i>
<i v-else class="fas fa-clock icon requesting"></i>
<span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
</div>
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
</div>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormButton from '@/components/debobigego/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormButton,
MkButton,
MkInput,
},
emits: ['info'],
@ -37,6 +32,12 @@ export default defineComponent({
title: this.$ts.relays,
icon: 'fas fa-globe',
bg: 'var(--bg)',
actions: [{
asFullButton: true,
icon: 'fas fa-plus',
text: this.$ts.addRelay,
handler: this.addRelay,
}],
},
relays: [],
inbox: '',
@ -47,10 +48,6 @@ export default defineComponent({
this.refresh();
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async addRelay() {
const { canceled, result: inbox } = await os.inputText({
@ -94,5 +91,22 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
.relaycxt {
> .status {
margin: 8px 0;
> .icon {
width: 1em;
margin-right: 0.75em;
&.accepted {
color: var(--success);
}
&.rejected {
color: var(--error);
}
}
}
}
</style>

View File

@ -1,44 +1,58 @@
<template>
<FormBase>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormLink to="/admin/bot-protection">
<i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}
<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
<template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template>
</FormLink>
<div class="_formRoot">
<FormFolder class="_formBlock">
<template #icon><i class="fas fa-shield-alt"></i></template>
<template #label>{{ $ts.botProtection }}</template>
<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
<template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template>
<FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch>
<XBotProtection/>
</FormFolder>
<FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch>
<FormFolder class="_formBlock">
<template #label>Summaly Proxy</template>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
<div class="_formRoot">
<FormInput v-model="summalyProxy" class="_formBlock">
<template #prefix><i class="fas fa-link"></i></template>
<template #label>Summaly Proxy URL</template>
</FormInput>
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</div>
</FormFolder>
</div>
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import FormLink from '@/components/debobigego/link.vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormFolder from '@/components/form/folder.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSection from '@/components/form/section.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import XBotProtection from './bot-protection.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
FormLink,
FormFolder,
FormSwitch,
FormBase,
FormGroup,
FormButton,
FormInfo,
FormSection,
FormSuspense,
FormButton,
FormInput,
XBotProtection,
},
emits: ['info'],
@ -50,30 +64,23 @@ export default defineComponent({
icon: 'fas fa-lock',
bg: 'var(--bg)',
},
summalyProxy: '',
enableHcaptcha: false,
enableRecaptcha: false,
enableRegistration: false,
emailRequiredForSignup: false,
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
this.summalyProxy = meta.summalyProxy;
this.enableHcaptcha = meta.enableHcaptcha;
this.enableRecaptcha = meta.enableRecaptcha;
this.enableRegistration = !meta.disableRegistration;
this.emailRequiredForSignup = meta.emailRequiredForSignup;
},
save() {
os.apiWithDialog('admin/update-meta', {
disableRegistration: !this.enableRegistration,
emailRequiredForSignup: this.emailRequiredForSignup,
summalyProxy: this.summalyProxy,
}).then(() => {
fetchInstance();
});

View File

@ -1,85 +0,0 @@
<template>
<FormBase>
<FormSuspense :p="init">
<FormSwitch v-model="enableServiceWorker">
{{ $ts.enableServiceworker }}
<template #desc>{{ $ts.serviceworkerInfo }}</template>
</FormSwitch>
<template v-if="enableServiceWorker">
<FormInput v-model="swPublicKey">
<template #prefix><i class="fas fa-key"></i></template>
Public key
</FormInput>
<FormInput v-model="swPrivateKey">
<template #prefix><i class="fas fa-key"></i></template>
Private key
</FormInput>
</template>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormSuspense>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormGroup,
FormButton,
FormSuspense,
},
emits: ['info'],
data() {
return {
[symbols.PAGE_INFO]: {
title: 'ServiceWorker',
icon: 'fas fa-bolt',
bg: 'var(--bg)',
},
enableServiceWorker: false,
swPublicKey: null,
swPrivateKey: null,
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
this.enableServiceWorker = meta.enableServiceWorker;
this.swPublicKey = meta.swPublickey;
this.swPrivateKey = meta.swPrivateKey;
},
save() {
os.apiWithDialog('admin/update-meta', {
enableServiceWorker: this.enableServiceWorker,
swPublicKey: this.swPublicKey,
swPrivateKey: this.swPrivateKey,
}).then(() => {
fetchInstance();
});
}
}
});
</script>

View File

@ -1,72 +1,146 @@
<template>
<FormBase>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormInput v-model="name">
<span>{{ $ts.instanceName }}</span>
</FormInput>
<div class="_formRoot">
<FormInput v-model="name" class="_formBlock">
<template #label>{{ $ts.instanceName }}</template>
</FormInput>
<FormTextarea v-model="description">
<span>{{ $ts.instanceDescription }}</span>
</FormTextarea>
<FormTextarea v-model="description" class="_formBlock">
<template #label>{{ $ts.instanceDescription }}</template>
</FormTextarea>
<FormInput v-model="iconUrl">
<template #prefix><i class="fas fa-link"></i></template>
<span>{{ $ts.iconUrl }}</span>
</FormInput>
<FormInput v-model="iconUrl" class="_formBlock">
<template #prefix><i class="fas fa-link"></i></template>
<template #label>{{ $ts.iconUrl }}</template>
</FormInput>
<FormInput v-model="bannerUrl">
<template #prefix><i class="fas fa-link"></i></template>
<span>{{ $ts.bannerUrl }}</span>
</FormInput>
<FormInput v-model="bannerUrl" class="_formBlock">
<template #prefix><i class="fas fa-link"></i></template>
<template #label>{{ $ts.bannerUrl }}</template>
</FormInput>
<FormInput v-model="backgroundImageUrl">
<template #prefix><i class="fas fa-link"></i></template>
<span>{{ $ts.backgroundImageUrl }}</span>
</FormInput>
<FormInput v-model="backgroundImageUrl" class="_formBlock">
<template #prefix><i class="fas fa-link"></i></template>
<template #label>{{ $ts.backgroundImageUrl }}</template>
</FormInput>
<FormInput v-model="tosUrl">
<template #prefix><i class="fas fa-link"></i></template>
<span>{{ $ts.tosUrl }}</span>
</FormInput>
<FormInput v-model="tosUrl" class="_formBlock">
<template #prefix><i class="fas fa-link"></i></template>
<template #label>{{ $ts.tosUrl }}</template>
</FormInput>
<FormInput v-model="maintainerName">
<span>{{ $ts.maintainerName }}</span>
</FormInput>
<FormSplit :min-width="300">
<FormInput v-model="maintainerName" class="_formBlock">
<template #label>{{ $ts.maintainerName }}</template>
</FormInput>
<FormInput v-model="maintainerEmail" type="email">
<template #prefix><i class="fas fa-envelope"></i></template>
<span>{{ $ts.maintainerEmail }}</span>
</FormInput>
<FormInput v-model="maintainerEmail" type="email" class="_formBlock">
<template #prefix><i class="fas fa-envelope"></i></template>
<template #label>{{ $ts.maintainerEmail }}</template>
</FormInput>
</FormSplit>
<FormTextarea v-model="pinnedUsers">
<span>{{ $ts.pinnedUsers }}</span>
<template #desc>{{ $ts.pinnedUsersDescription }}</template>
</FormTextarea>
<FormTextarea v-model="pinnedUsers" class="_formBlock">
<template #label>{{ $ts.pinnedUsers }}</template>
<template #caption>{{ $ts.pinnedUsersDescription }}</template>
</FormTextarea>
<FormInput v-model="maxNoteTextLength" type="number">
<template #prefix><i class="fas fa-pencil-alt"></i></template>
<span>{{ $ts.maxNoteTextLength }}</span>
</FormInput>
<FormInput v-model="maxNoteTextLength" type="number" class="_formBlock">
<template #prefix><i class="fas fa-pencil-alt"></i></template>
<template #label>{{ $ts.maxNoteTextLength }}</template>
</FormInput>
<FormSwitch v-model="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch>
<FormSwitch v-model="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch>
<FormInfo>{{ $ts.disablingTimelinesInfo }}</FormInfo>
<FormSection>
<FormSwitch v-model="enableRegistration" class="_formBlock">
<template #label>{{ $ts.enableRegistration }}</template>
</FormSwitch>
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
<FormSwitch v-model="emailRequiredForSignup" class="_formBlock">
<template #label>{{ $ts.emailRequiredForSignup }}</template>
</FormSwitch>
</FormSection>
<FormSection>
<FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch>
<FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch>
<FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo>
</FormSection>
<FormSection>
<template #label>{{ $ts.files }}</template>
<FormSwitch v-model="cacheRemoteFiles" class="_formBlock">
<template #label>{{ $ts.cacheRemoteFiles }}</template>
<template #caption>{{ $ts.cacheRemoteFilesDescription }}</template>
</FormSwitch>
<FormSwitch v-model="proxyRemoteFiles" class="_formBlock">
<template #label>{{ $ts.proxyRemoteFiles }}</template>
<template #caption>{{ $ts.proxyRemoteFilesDescription }}</template>
</FormSwitch>
<FormSplit :min-width="280">
<FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock">
<template #label>{{ $ts.driveCapacityPerLocalAccount }}</template>
<template #suffix>MB</template>
<template #caption>{{ $ts.inMb }}</template>
</FormInput>
<FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock">
<template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template>
<template #suffix>MB</template>
<template #caption>{{ $ts.inMb }}</template>
</FormInput>
</FormSplit>
</FormSection>
<FormSection>
<template #label>ServiceWorker</template>
<FormSwitch v-model="enableServiceWorker" class="_formBlock">
<template #label>{{ $ts.enableServiceworker }}</template>
<template #caption>{{ $ts.serviceworkerInfo }}</template>
</FormSwitch>
<template v-if="enableServiceWorker">
<FormInput v-model="swPublicKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Public key</template>
</FormInput>
<FormInput v-model="swPrivateKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Private key</template>
</FormInput>
</template>
</FormSection>
<FormSection>
<template #label>DeepL Translation</template>
<FormInput v-model="deeplAuthKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>DeepL Auth Key</template>
</FormInput>
<FormSwitch v-model="deeplIsPro" class="_formBlock">
<template #label>Pro account</template>
</FormSwitch>
</FormSection>
</div>
</FormSuspense>
</FormBase>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormTextarea from '@/components/debobigego/textarea.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSection from '@/components/form/section.vue';
import FormSplit from '@/components/form/split.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { fetchInstance } from '@/instance';
@ -75,12 +149,11 @@ export default defineComponent({
components: {
FormSwitch,
FormInput,
FormBase,
FormGroup,
FormButton,
FormSuspense,
FormTextarea,
FormInfo,
FormSuspense,
FormSection,
FormSplit,
},
emits: ['info'],
@ -91,6 +164,12 @@ export default defineComponent({
title: this.$ts.general,
icon: 'fas fa-cog',
bg: 'var(--bg)',
actions: [{
asFullButton: true,
icon: 'fas fa-check',
text: this.$ts.save,
handler: this.save,
}],
},
name: null,
description: null,
@ -104,13 +183,20 @@ export default defineComponent({
enableLocalTimeline: false,
enableGlobalTimeline: false,
pinnedUsers: '',
cacheRemoteFiles: false,
proxyRemoteFiles: false,
localDriveCapacityMb: 0,
remoteDriveCapacityMb: 0,
enableRegistration: false,
emailRequiredForSignup: false,
enableServiceWorker: false,
swPublicKey: null,
swPrivateKey: null,
deeplAuthKey: '',
deeplIsPro: false,
}
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async init() {
const meta = await os.api('meta', { detail: true });
@ -126,6 +212,17 @@ export default defineComponent({
this.enableLocalTimeline = !meta.disableLocalTimeline;
this.enableGlobalTimeline = !meta.disableGlobalTimeline;
this.pinnedUsers = meta.pinnedUsers.join('\n');
this.cacheRemoteFiles = meta.cacheRemoteFiles;
this.proxyRemoteFiles = meta.proxyRemoteFiles;
this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
this.enableRegistration = !meta.disableRegistration;
this.emailRequiredForSignup = meta.emailRequiredForSignup;
this.enableServiceWorker = meta.enableServiceWorker;
this.swPublicKey = meta.swPublickey;
this.swPrivateKey = meta.swPrivateKey;
this.deeplAuthKey = meta.deeplAuthKey;
this.deeplIsPro = meta.deeplIsPro;
},
save() {
@ -142,6 +239,17 @@ export default defineComponent({
disableLocalTimeline: !this.enableLocalTimeline,
disableGlobalTimeline: !this.enableGlobalTimeline,
pinnedUsers: this.pinnedUsers.split('\n'),
cacheRemoteFiles: this.cacheRemoteFiles,
proxyRemoteFiles: this.proxyRemoteFiles,
localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
disableRegistration: !this.enableRegistration,
emailRequiredForSignup: this.emailRequiredForSignup,
enableServiceWorker: this.enableServiceWorker,
swPublicKey: this.swPublicKey,
swPrivateKey: this.swPrivateKey,
deeplAuthKey: this.deeplAuthKey,
deeplIsPro: this.deeplIsPro,
}).then(() => {
fetchInstance();
});

View File

@ -30,7 +30,7 @@
<template #prefix>@</template>
<template #label>{{ $ts.username }}</template>
</MkInput>
<MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'" @update:modelValue="$refs.users.reload()">
<MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()">
<template #prefix>@</template>
<template #label>{{ $ts.host }}</template>
</MkInput>
@ -62,7 +62,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { computed, defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
@ -110,36 +110,20 @@ export default defineComponent({
searchUsername: '',
searchHost: '',
pagination: {
endpoint: 'admin/show-users',
endpoint: 'admin/show-users' as const,
limit: 10,
params: () => ({
params: computed(() => ({
sort: this.sort,
state: this.state,
origin: this.origin,
username: this.searchUsername,
hostname: this.searchHost,
}),
})),
offsetMode: true
},
}
},
watch: {
sort() {
this.$refs.users.reload();
},
state() {
this.$refs.users.reload();
},
origin() {
this.$refs.users.reload();
},
},
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
lookupUser,

View File

@ -1,349 +0,0 @@
<template>
<div class="t9makv94">
<section class="_section">
<div class="_content">
<details>
<summary>{{ $ts.import }}</summary>
<MkTextarea v-model="themeToImport">
{{ $ts._theme.importInfo }}
</MkTextarea>
<MkButton :disabled="!themeToImport.trim()" @click="importTheme">{{ $ts.import }}</MkButton>
</details>
</div>
</section>
<section class="_section">
<div class="_content _card _gap">
<div class="_content">
<MkInput v-model="name" required><span>{{ $ts.name }}</span></MkInput>
<MkInput v-model="author" required><span>{{ $ts.author }}</span></MkInput>
<MkTextarea v-model="description"><span>{{ $ts.description }}</span></MkTextarea>
<div class="_inputs">
<div v-text="$ts._theme.base" />
<MkRadio v-model="baseTheme" value="light">{{ $ts.light }}</MkRadio>
<MkRadio v-model="baseTheme" value="dark">{{ $ts.dark }}</MkRadio>
</div>
</div>
</div>
<div class="_content _card _gap">
<div class="list-view _content">
<div v-for="([ k, v ], i) in theme" :key="k" class="item">
<div class="_inputs">
<div>
{{ k.startsWith('$') ? `${k} (${$ts._theme.constant})` : $t('_theme.keys.' + k) }}
<button v-if="k.startsWith('$')" class="_button _link" @click="del(i)" v-text="$ts.delete" />
</div>
<div>
<div class="type" @click="chooseType($event, i)">
{{ getTypeOf(v) }} <i class="fas fa-chevron-down"></i>
</div>
<!-- default -->
<div v-if="v === null" class="default-value" v-text="baseProps[k]" />
<!-- color -->
<div v-else-if="typeof v === 'string'" class="color">
<input type="color" :value="v" @input="colorChanged($event.target.value, i)"/>
<MkInput class="select" :value="v" @update:modelValue="colorChanged($event, i)"/>
</div>
<!-- ref const -->
<MkInput v-else-if="v.type === 'refConst'" v-model="v.key">
<template #prefix>$</template>
<span>{{ $ts.name }}</span>
</MkInput>
<!-- ref props -->
<MkSelect v-else-if="v.type === 'refProp'" v-model="v.key" class="select">
<option v-for="key in themeProps" :key="key" :value="key">{{ $t('_theme.keys.' + key) }}</option>
</MkSelect>
<!-- func -->
<template v-else-if="v.type === 'func'">
<MkSelect v-model="v.name" class="select">
<template #label>{{ $ts._theme.funcKind }}</template>
<option v-for="n in ['alpha', 'darken', 'lighten']" :key="n" :value="n">{{ $t('_theme.' + n) }}</option>
</MkSelect>
<MkInput v-model="v.arg" type="number"><span>{{ $ts._theme.argument }}</span></MkInput>
<MkSelect v-model="v.value" class="select">
<template #label>{{ $ts._theme.basedProp }}</template>
<option v-for="key in themeProps" :key="key" :value="key">{{ $t('_theme.keys.' + key) }}</option>
</MkSelect>
</template>
<!-- CSS -->
<MkInput v-else-if="v.type === 'css'" v-model="v.value">
<span>CSS</span>
</MkInput>
</div>
</div>
</div>
<MkButton primary @click="addConst">{{ $ts._theme.addConstant }}</MkButton>
</div>
</div>
</section>
<section class="_section">
<details class="_content">
<summary>{{ $ts.sample }}</summary>
<MkSample/>
</details>
</section>
<section class="_section">
<div class="_content">
<MkButton inline @click="preview">{{ $ts.preview }}</MkButton>
<MkButton inline primary :disabled="!name || !author" @click="save">{{ $ts.save }}</MkButton>
</div>
</section>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as JSON5 from 'json5';
import { toUnicode } from 'punycode/';
import MkRadio from '@/components/form/radio.vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkSelect from '@/components/form/select.vue';
import MkSample from '@/components/sample.vue';
import { convertToMisskeyTheme, ThemeValue, convertToViewModel, ThemeViewModel } from '@/scripts/theme-editor';
import { Theme, applyTheme, lightTheme, darkTheme, themeProps, validateTheme } from '@/scripts/theme';
import { host } from '@/config';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { addTheme } from '@/theme-store';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
MkRadio,
MkButton,
MkInput,
MkTextarea,
MkSelect,
MkSample,
},
async beforeRouteLeave(to, from, next) {
if (this.changed && !(await this.confirm())) {
next(false);
} else {
next();
}
},
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.themeEditor,
icon: 'fas fa-palette',
},
theme: [] as ThemeViewModel,
name: '',
description: '',
baseTheme: 'light' as 'dark' | 'light',
author: `@${this.$i.username}@${toUnicode(host)}`,
themeToImport: '',
changed: false,
lightTheme, darkTheme, themeProps,
}
},
computed: {
baseProps() {
return this.baseTheme === 'light' ? this.lightTheme.props : this.darkTheme.props;
},
},
beforeUnmount() {
window.removeEventListener('beforeunload', this.beforeunload);
},
mounted() {
this.init();
window.addEventListener('beforeunload', this.beforeunload);
const changed = () => this.changed = true;
this.$watch('name', changed);
this.$watch('description', changed);
this.$watch('baseTheme', changed);
this.$watch('author', changed);
this.$watch('theme', changed);
},
methods: {
beforeunload(e: BeforeUnloadEvent) {
if (this.changed) {
e.preventDefault();
e.returnValue = '';
}
},
async confirm(): Promise<boolean> {
const { canceled } = await os.confirm({
type: 'warning',
text: this.$ts.leaveConfirm,
});
return !canceled;
},
init() {
const t: ThemeViewModel = [];
for (const key of themeProps) {
t.push([ key, null ]);
}
this.theme = t;
},
async del(i: number) {
const { canceled } = await os.confirm({
type: 'warning',
text: this.$t('_theme.deleteConstantConfirm', { const: this.theme[i][0] }),
});
if (canceled) return;
Vue.delete(this.theme, i);
},
async addConst() {
const { canceled, result } = await os.inputText({
title: this.$ts._theme.inputConstantName,
});
if (canceled) return;
this.theme.push([ '$' + result, '#000000']);
},
save() {
const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme);
addTheme(theme);
os.alert({
type: 'success',
text: this.$t('_theme.installed', { name: theme.name })
});
this.changed = false;
},
preview() {
const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme);
try {
applyTheme(theme, false);
} catch (e) {
os.alert({
type: 'error',
text: e.message
});
}
},
async importTheme() {
if (this.changed && (!await this.confirm())) return;
try {
const theme = JSON5.parse(this.themeToImport) as Theme;
if (!validateTheme(theme)) throw new Error(this.$ts._theme.invalid);
this.name = theme.name;
this.description = theme.desc || '';
this.author = theme.author;
this.baseTheme = theme.base || 'light';
this.theme = convertToViewModel(theme);
this.themeToImport = '';
} catch (e) {
os.alert({
type: 'error',
text: e.message
});
}
},
colorChanged(color: string, i: number) {
this.theme[i] = [this.theme[i][0], color];
},
getTypeOf(v: ThemeValue) {
return v === null
? this.$ts._theme.defaultValue
: typeof v === 'string'
? this.$ts._theme.color
: this.$t('_theme.' + v.type);
},
async chooseType(e: MouseEvent, i: number) {
const newValue = await this.showTypeMenu(e);
this.theme[i] = [ this.theme[i][0], newValue ];
},
showTypeMenu(e: MouseEvent) {
return new Promise<ThemeValue>((resolve) => {
os.popupMenu([{
text: this.$ts._theme.defaultValue,
action: () => resolve(null),
}, {
text: this.$ts._theme.color,
action: () => resolve('#000000'),
}, {
text: this.$ts._theme.func,
action: () => resolve({
type: 'func', name: 'alpha', arg: 1, value: 'accent'
}),
}, {
text: this.$ts._theme.refProp,
action: () => resolve({
type: 'refProp', key: 'accent',
}),
}, {
text: this.$ts._theme.refConst,
action: () => resolve({
type: 'refConst', key: '',
}),
}, {
text: 'CSS',
action: () => resolve({
type: 'css', value: '',
}),
}], e.currentTarget || e.target);
});
}
}
});
</script>
<style lang="scss" scoped>
.t9makv94 {
> ._section {
> ._content {
> .list-view {
> .item {
min-height: 48px;
word-break: break-all;
&:not(:last-child) {
margin-bottom: 8px;
}
.select {
margin: 24px 0;
}
.type {
cursor: pointer;
}
.default-value {
opacity: 0.6;
pointer-events: none;
user-select: none;
}
.color {
> input {
display: inline-block;
width: 1.5em;
height: 1.5em;
}
> div {
margin-left: 8px;
display: inline-block;
}
}
}
}
}
}
}
</style>

View File

@ -36,7 +36,7 @@ export default defineComponent({
bg: 'var(--bg)',
},
pagination: {
endpoint: 'announcements',
endpoint: 'announcements' as const,
limit: 10,
},
};

View File

@ -67,11 +67,11 @@ export default defineComponent({
channel: null,
showBanner: true,
pagination: {
endpoint: 'channels/timeline',
endpoint: 'channels/timeline' as const,
limit: 10,
params: () => ({
params: computed(() => ({
channelId: this.channelId,
})
}))
},
};
},

View File

@ -60,15 +60,15 @@ export default defineComponent({
})),
tab: 'featured',
featuredPagination: {
endpoint: 'channels/featured',
endpoint: 'channels/featured' as const,
noPaging: true,
},
followingPagination: {
endpoint: 'channels/followed',
endpoint: 'channels/followed' as const,
limit: 5,
},
ownedPagination: {
endpoint: 'channels/owned',
endpoint: 'channels/owned' as const,
limit: 5,
},
};

View File

@ -50,11 +50,11 @@ export default defineComponent({
} : null),
clip: null,
pagination: {
endpoint: 'clips/notes',
endpoint: 'clips/notes' as const,
limit: 10,
params: () => ({
params: computed(() => ({
clipId: this.clipId,
})
}))
},
};
},

View File

@ -4,27 +4,21 @@
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script lang="ts" setup>
import { computed } from 'vue';
import XDrive from '@/components/drive.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
XDrive
},
let folder = $ref(null);
data() {
return {
[symbols.PAGE_INFO]: {
title: computed(() => this.folder ? this.folder.name : this.$ts.drive),
icon: 'fas fa-cloud',
bg: 'var(--bg)',
hideHeader: true,
},
folder: null,
};
},
defineExpose({
[symbols.PAGE_INFO]: computed(() => ({
title: folder ? folder.name : i18n.locale.drive,
icon: 'fas fa-cloud',
bg: 'var(--bg)',
hideHeader: true,
})),
});
</script>

View File

@ -8,35 +8,29 @@
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { } from 'vue';
import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { i18n } from '@/i18n';
export default defineComponent({
props: {
emoji: {
type: Object,
required: true,
}
},
const props = defineProps<{
emoji: Record<string, unknown>; // TODO
}>();
methods: {
menu(ev) {
os.popupMenu([{
type: 'label',
text: ':' + this.emoji.name + ':',
}, {
text: this.$ts.copy,
icon: 'fas fa-copy',
action: () => {
copyToClipboard(`:${this.emoji.name}:`);
os.success();
}
}], ev.currentTarget || ev.target);
function menu(ev) {
os.popupMenu([{
type: 'label',
text: ':' + props.emoji.name + ':',
}, {
text: i18n.locale.copy,
icon: 'fas fa-copy',
action: () => {
copyToClipboard(`:${props.emoji.name}:`);
os.success();
}
}
});
}], ev.currentTarget || ev.target);
}
</script>
<style lang="scss" scoped>

View File

@ -4,55 +4,47 @@
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import XCategory from './emojis.category.vue';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
XCategory,
},
const tab = ref('category');
data() {
return {
[symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.customEmojis,
icon: 'fas fa-laugh',
bg: 'var(--bg)',
actions: [{
icon: 'fas fa-ellipsis-h',
handler: this.menu
}],
})),
tab: 'category',
function menu(ev) {
os.popupMenu([{
icon: 'fas fa-download',
text: i18n.locale.export,
action: async () => {
os.api('export-custom-emojis', {
})
.then(() => {
os.alert({
type: 'info',
text: i18n.locale.exportRequested,
});
}).catch((e) => {
os.alert({
type: 'error',
text: e.message,
});
});
}
},
}], ev.currentTarget || ev.target);
}
methods: {
menu(ev) {
os.popupMenu([{
icon: 'fas fa-download',
text: this.$ts.export,
action: async () => {
os.api('export-custom-emojis', {
})
.then(() => {
os.alert({
type: 'info',
text: this.$ts.exportRequested,
});
}).catch((e) => {
os.alert({
type: 'error',
text: e.message,
});
});
}
}], ev.currentTarget || ev.target);
}
}
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.customEmojis,
icon: 'fas fa-laugh',
bg: 'var(--bg)',
actions: [{
icon: 'fas fa-ellipsis-h',
handler: menu,
}],
},
});
</script>

View File

@ -156,7 +156,7 @@ export default defineComponent({
sort: '+createdAt',
} },
searchPagination: {
endpoint: 'users/search',
endpoint: 'users/search' as const,
limit: 10,
params: computed(() => (this.searchQuery && this.searchQuery !== '') ? {
query: this.searchQuery,
@ -178,7 +178,7 @@ export default defineComponent({
},
tagUsers(): any {
return {
endpoint: 'hashtags/users',
endpoint: 'hashtags/users' as const,
limit: 30,
params: {
tag: this.tag,

View File

@ -1,49 +1,49 @@
<template>
<div class="jmelgwjh">
<div class="body">
<XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'"/>
</div>
</div>
<MkSpacer :content-max="800">
<MkPagination ref="pagingComponent" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
<div>{{ $ts.noNotes }}</div>
</div>
</template>
<template #default="{ items }">
<XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false">
<XNote :key="item.id" :note="item.note" :class="$style.note"/>
</XList>
</template>
</MkPagination>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import XNotes from '@/components/notes.vue';
import * as os from '@/os';
<script lang="ts" setup>
import { ref } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import XNote from '@/components/note.vue';
import XList from '@/components/date-separated-list.vue';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
XNotes
},
const pagination = {
endpoint: 'i/favorites' as const,
limit: 10,
};
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.favorites,
icon: 'fas fa-star',
bg: 'var(--bg)',
},
pagination: {
endpoint: 'i/favorites',
limit: 10,
params: () => ({
})
},
};
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.favorites,
icon: 'fas fa-star',
bg: 'var(--bg)',
},
});
</script>
<style lang="scss" scoped>
.jmelgwjh {
background: var(--bg);
> .body {
box-sizing: border-box;
max-width: 800px;
margin: 0 auto;
padding: 16px;
}
<style lang="scss" module>
.note {
background: var(--panel);
border-radius: var(--radius);
}
</style>

View File

@ -4,29 +4,22 @@
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
XNotes
},
const pagination = {
endpoint: 'notes/featured' as const,
limit: 10,
offsetMode: true,
};
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.featured,
icon: 'fas fa-fire-alt',
bg: 'var(--bg)',
},
pagination: {
endpoint: 'notes/featured',
limit: 10,
offsetMode: true,
},
};
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.featured,
icon: 'fas fa-fire-alt',
bg: 'var(--bg)',
},
});
</script>

View File

@ -6,7 +6,7 @@
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.host }}</template>
</MkInput>
<div class="_inputSplit" style="margin-top: var(--margin);">
<FormSplit style="margin-top: var(--margin);">
<MkSelect v-model="state">
<template #label>{{ $ts.state }}</template>
<option value="all">{{ $ts.all }}</option>
@ -38,7 +38,7 @@
<option value="+driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.descendingOrder }})</option>
<option value="-driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.ascendingOrder }})</option>
</MkSelect>
</div>
</FormSplit>
</div>
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
@ -95,75 +95,50 @@
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { computed } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
import MkPagination from '@/components/ui/pagination.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
MkButton,
MkInput,
MkSelect,
MkPagination,
let host = $ref('');
let state = $ref('federating');
let sort = $ref('+pubSub');
const pagination = {
endpoint: 'federation/instances' as const,
limit: 10,
offsetMode: true,
params: computed(() => ({
sort: sort,
host: host != '' ? host : null,
...(
state === 'federating' ? { federating: true } :
state === 'subscribing' ? { subscribing: true } :
state === 'publishing' ? { publishing: true } :
state === 'suspended' ? { suspended: true } :
state === 'blocked' ? { blocked: true } :
state === 'notResponding' ? { notResponding: true } :
{})
}))
};
function getStatus(instance) {
if (instance.isSuspended) return 'suspended';
if (instance.isNotResponding) return 'error';
return 'alive';
};
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.federation,
icon: 'fas fa-globe',
bg: 'var(--bg)',
},
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>

View File

@ -1,6 +1,6 @@
<template>
<div>
<MkPagination ref="list" :pagination="pagination" class="mk-follow-requests">
<MkPagination ref="paginationComponent" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@ -8,19 +8,21 @@
</div>
</template>
<template v-slot="{items}">
<div v-for="req in items" :key="req.id" class="user _panel">
<MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/>
<div class="body">
<div class="name">
<MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA>
<p class="acct">@{{ acct(req.follower) }}</p>
</div>
<div v-if="req.follower.description" class="description" :title="req.follower.description">
<Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/>
</div>
<div class="actions">
<button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button>
<button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button>
<div class="mk-follow-requests">
<div v-for="req in items" :key="req.id" class="user _panel">
<MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/>
<div class="body">
<div class="name">
<MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA>
<p class="acct">@{{ acct(req.follower) }}</p>
</div>
<div v-if="req.follower.description" class="description" :title="req.follower.description">
<Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/>
</div>
<div class="actions">
<button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button>
<button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button>
</div>
</div>
</div>
</div>
@ -29,45 +31,39 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { ref, computed } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import { userPage, acct } from '@/filters/user';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
MkPagination
},
const paginationComponent = ref<InstanceType<typeof MkPagination>>();
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.followRequests,
icon: 'fas fa-user-clock',
},
pagination: {
endpoint: 'following/requests/list',
limit: 10,
},
};
},
const pagination = {
endpoint: 'following/requests/list' as const,
limit: 10,
};
methods: {
accept(user) {
os.api('following/requests/accept', { userId: user.id }).then(() => {
this.$refs.list.reload();
});
},
reject(user) {
os.api('following/requests/reject', { userId: user.id }).then(() => {
this.$refs.list.reload();
});
},
userPage,
acct
}
function accept(user) {
os.api('following/requests/accept', { userId: user.id }).then(() => {
paginationComponent.value.reload();
});
}
function reject(user) {
os.api('following/requests/reject', { userId: user.id }).then(() => {
paginationComponent.value.reload();
});
}
defineExpose({
[symbols.PAGE_INFO]: computed(() => ({
title: i18n.locale.followRequests,
icon: 'fas fa-user-clock',
bg: 'var(--bg)',
})),
});
</script>

View File

@ -1,16 +1,16 @@
<template>
<FormBase>
<div>
<FormSuspense :p="init">
<FormInput v-model="title">
<span>{{ $ts.title }}</span>
<template #label>{{ $ts.title }}</template>
</FormInput>
<FormTextarea v-model="description" :max="500">
<span>{{ $ts.description }}</span>
<template #label>{{ $ts.description }}</template>
</FormTextarea>
<FormGroup>
<div v-for="file in files" :key="file.id" class="_debobigegoItem _debobigegoPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
<div v-for="file in files" :key="file.id" class="_formGroup wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
<div class="name">{{ file.name }}</div>
<button v-tooltip="$ts.remove" class="remove _button" @click="remove(file)"><i class="fas fa-times"></i></button>
</div>
@ -24,19 +24,17 @@
<FormButton v-if="postId" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton>
</FormSuspense>
</FormBase>
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import FormButton from '@/components/debobigego/button.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormTextarea from '@/components/debobigego/textarea.vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormTuple from '@/components/debobigego/tuple.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormButton from '@/components/ui/button.vue';
import FormInput from '@/components/form/input.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormGroup from '@/components/form/group.vue';
import FormSuspense from '@/components/form/suspense.vue';
import { selectFiles } from '@/scripts/select-file';
import * as os from '@/os';
import * as symbols from '@/symbols';
@ -47,7 +45,6 @@ export default defineComponent({
FormInput,
FormTextarea,
FormSwitch,
FormBase,
FormGroup,
FormSuspense,
},

View File

@ -81,19 +81,19 @@ export default defineComponent({
},
tab: 'explore',
recentPostsPagination: {
endpoint: 'gallery/posts',
endpoint: 'gallery/posts' as const,
limit: 6,
},
popularPostsPagination: {
endpoint: 'gallery/featured',
endpoint: 'gallery/featured' as const,
limit: 5,
},
myPostsPagination: {
endpoint: 'i/gallery/posts',
endpoint: 'i/gallery/posts' as const,
limit: 5,
},
likedPostsPagination: {
endpoint: 'i/gallery/likes',
endpoint: 'i/gallery/likes' as const,
limit: 5,
},
tags: [],
@ -106,7 +106,7 @@ export default defineComponent({
},
tagUsers(): any {
return {
endpoint: 'hashtags/users',
endpoint: 'hashtags/users' as const,
limit: 30,
params: {
tag: this.tag,

View File

@ -1,6 +1,6 @@
<template>
<div class="_root">
<transition name="fade" mode="out-in">
<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="post" class="rkxwuolj">
<div class="files">
<div v-for="file in post.files" :key="file.id" class="file">
@ -93,11 +93,11 @@ export default defineComponent({
}]
} : null),
otherPostsPagination: {
endpoint: 'users/gallery/posts',
endpoint: 'users/gallery/posts' as const,
limit: 6,
params: () => ({
params: computed(() => ({
userId: this.post.user.id
})
})),
},
post: null,
error: null,

View File

@ -1,70 +1,71 @@
<template>
<FormBase>
<FormGroup v-if="instance">
<template #label>{{ instance.host }}</template>
<FormGroup>
<div class="_debobigegoItem">
<div class="_debobigegoPanel fnfelxur">
<img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/>
</div>
</div>
<FormKeyValueView>
<template #key>Name</template>
<template #value><span class="_monospace">{{ instance.name || `(${$ts.unknown})` }}</span></template>
</FormKeyValueView>
</FormGroup>
<MkSpacer :content-max="600" :margin-min="16" :margin-max="32">
<div v-if="instance" class="_formRoot">
<div class="fnfelxur">
<img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/>
</div>
<MkKeyValue :copy="host" oneline style="margin: 1em 0;">
<template #key>Host</template>
<template #value><span class="_monospace"><MkLink :url="`https://${host}`">{{ host }}</MkLink></span></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>Name</template>
<template #value>{{ instance.name || `(${$ts.unknown})` }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ $ts.description }}</template>
<template #value>{{ instance.description }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.software }}</template>
<template #value><span class="_monospace">{{ instance.softwareName || `(${$ts.unknown})` }} / {{ instance.softwareVersion || `(${$ts.unknown})` }}</span></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.administrator }}</template>
<template #value>{{ instance.maintainerName || `(${$ts.unknown})` }} ({{ instance.maintainerEmail || `(${$ts.unknown})` }})</template>
</MkKeyValue>
<FormButton v-if="$i.isAdmin || $i.isModerator" primary @click="info">{{ $ts.settings }}</FormButton>
<FormSection v-if="iAmModerator">
<template #label>Moderation</template>
<FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.stopActivityDelivery }}</FormSwitch>
<FormSwitch v-model="isBlocked" class="_formBlock" @update:modelValue="toggleBlock">{{ $ts.blockThisInstance }}</FormSwitch>
</FormSection>
<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>
<FormSection>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.registeredAt }}</template>
<template #value><MkTime mode="detail" :time="instance.caughtAt"/></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.updatedAt }}</template>
<template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.latestRequestSentAt }}</template>
<template #value><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.latestStatus }}</template>
<template #value>{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.latestRequestReceivedAt }}</template>
<template #value><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template>
</FormKeyValueView>
</FormGroup>
<FormGroup>
<FormKeyValueView>
</MkKeyValue>
</FormSection>
<FormSection>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>Open Registrations</template>
<template #value>{{ instance.openRegistrations ? $ts.yes : $ts.no }}</template>
</FormKeyValueView>
</FormGroup>
<div class="_debobigegoItem">
<div class="_debobigegoLabel">{{ $ts.statistics }}</div>
<div class="_debobigegoPanel cmhjzshl">
</MkKeyValue>
</FormSection>
<FormSection>
<template #label>{{ $ts.statistics }}</template>
<div class="cmhjzshl">
<div class="selects">
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
<option value="instance-requests">{{ $ts._instanceCharts.requests }}</option>
<option value="instance-users">{{ $ts._instanceCharts.users }}</option>
<option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option>
@ -83,147 +84,100 @@
</MkSelect>
</div>
<div class="chart">
<MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart>
<MkChart :src="chartSrc" :span="chartSpan" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
</div>
</div>
</div>
<FormGroup>
<FormKeyValueView>
<template #key>{{ $ts.registeredAt }}</template>
<template #value><MkTime mode="detail" :time="instance.caughtAt"/></template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $ts.updatedAt }}</template>
<template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
</FormKeyValueView>
</FormGroup>
<FormObjectView tall :value="instance">
<span>Raw</span>
</FormObjectView>
<FormGroup>
</FormSection>
<MkObjectView tall :value="instance">
</MkObjectView>
<FormSection>
<template #label>Well-known resources</template>
<FormLink :to="`https://${host}/.well-known/host-meta`" external>host-meta</FormLink>
<FormLink :to="`https://${host}/.well-known/host-meta.json`" external>host-meta.json</FormLink>
<FormLink :to="`https://${host}/.well-known/nodeinfo`" external>nodeinfo</FormLink>
<FormLink :to="`https://${host}/robots.txt`" external>robots.txt</FormLink>
<FormLink :to="`https://${host}/manifest.json`" external>manifest.json</FormLink>
</FormGroup>
<FormSuspense v-slot="{ result: dns }" :p="dnsPromiseFactory">
<FormGroup>
<template #label>DNS</template>
<FormKeyValueView v-for="record in dns.a" :key="record">
<template #key>A</template>
<template #value><span class="_monospace">{{ record }}</span></template>
</FormKeyValueView>
<FormKeyValueView v-for="record in dns.aaaa" :key="record">
<template #key>AAAA</template>
<template #value><span class="_monospace">{{ record }}</span></template>
</FormKeyValueView>
<FormKeyValueView v-for="record in dns.cname" :key="record">
<template #key>CNAME</template>
<template #value><span class="_monospace">{{ record }}</span></template>
</FormKeyValueView>
<FormKeyValueView v-for="record in dns.txt">
<template #key>TXT</template>
<template #value><span class="_monospace">{{ record[0] }}</span></template>
</FormKeyValueView>
</FormGroup>
</FormSuspense>
</FormGroup>
</FormBase>
<FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink>
<FormLink :to="`https://${host}/.well-known/host-meta.json`" external style="margin-bottom: 8px;">host-meta.json</FormLink>
<FormLink :to="`https://${host}/.well-known/nodeinfo`" external style="margin-bottom: 8px;">nodeinfo</FormLink>
<FormLink :to="`https://${host}/robots.txt`" external style="margin-bottom: 8px;">robots.txt</FormLink>
<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
</FormSection>
</div>
</MkSpacer>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
<script lang="ts" setup>
import { } from 'vue';
import * as misskey from 'misskey-js';
import MkChart from '@/components/chart.vue';
import FormObjectView from '@/components/debobigego/object-view.vue';
import FormTextarea from '@/components/debobigego/textarea.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import MkObjectView from '@/components/object-view.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/link.vue';
import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/key-value.vue';
import MkSelect from '@/components/form/select.vue';
import FormSwitch from '@/components/form/switch.vue';
import * as os from '@/os';
import number from '@/filters/number';
import bytes from '@/filters/bytes';
import * as symbols from '@/symbols';
import MkInstanceInfo from '@/pages/admin/instance.vue';
import { iAmModerator } from '@/account';
export default defineComponent({
components: {
FormBase,
FormTextarea,
FormObjectView,
FormButton,
FormLink,
FormGroup,
FormKeyValueView,
FormSuspense,
MkSelect,
MkChart,
const props = defineProps<{
host: string;
}>();
let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null);
let instance = $ref<misskey.entities.Instance | null>(null);
let suspended = $ref(false);
let isBlocked = $ref(false);
let chartSrc = $ref('instance-requests');
let chartSpan = $ref('hour');
async function fetch() {
meta = await os.api('meta', { detail: true });
instance = await os.api('federation/show-instance', {
host: props.host,
});
suspended = instance.isSuspended;
isBlocked = meta.blockedHosts.includes(instance.host);
}
async function toggleBlock(ev) {
if (meta == null) return;
await os.api('admin/update-meta', {
blockedHosts: isBlocked ? meta.blockedHosts.concat([instance.host]) : meta.blockedHosts.filter(x => x !== instance.host)
});
}
async function toggleSuspend(v) {
await os.api('admin/federation/update-instance', {
host: instance.host,
isSuspended: suspended,
});
}
fetch();
defineExpose({
[symbols.PAGE_INFO]: {
title: props.host,
icon: 'fas fa-info-circle',
bg: 'var(--bg)',
actions: [{
text: `https://${props.host}`,
icon: 'fas fa-external-link-alt',
handler: () => {
window.open(`https://${props.host}`, '_blank');
}
}],
},
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;
margin: 0;
height: 64px;
border-radius: 8px;
}
@ -232,7 +186,7 @@ export default defineComponent({
.cmhjzshl {
> .selects {
display: flex;
padding: 16px;
margin: 0 0 16px 0;
}
}
</style>

View File

@ -4,28 +4,21 @@
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
XNotes
},
const pagination = {
endpoint: 'notes/mentions' as const,
limit: 10,
};
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.mentions,
icon: 'fas fa-at',
bg: 'var(--bg)',
},
pagination: {
endpoint: 'notes/mentions',
limit: 10,
},
};
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.mentions,
icon: 'fas fa-at',
bg: 'var(--bg)',
},
});
</script>

View File

@ -4,31 +4,24 @@
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
XNotes
},
const pagination = {
endpoint: 'notes/mentions' as const,
limit: 10,
params: () => ({
visibility: 'specified'
}),
};
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.directNotes,
icon: 'fas fa-envelope',
bg: 'var(--bg)',
},
pagination: {
endpoint: 'notes/mentions',
limit: 10,
params: () => ({
visibility: 'specified'
})
},
};
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.directNotes,
icon: 'fas fa-envelope',
bg: 'var(--bg)',
},
});
</script>

View File

@ -44,6 +44,7 @@ import * as Acct from 'misskey-js/built/acct';
import MkButton from '@/components/ui/button.vue';
import { acct } from '@/filters/user';
import * as os from '@/os';
import { stream } from '@/stream';
import * as symbols from '@/symbols';
export default defineComponent({
@ -66,7 +67,7 @@ export default defineComponent({
},
mounted() {
this.connection = markRaw(os.stream.useChannel('messagingIndex'));
this.connection = markRaw(stream.useChannel('messagingIndex'));
this.connection.on('message', this.onMessage);
this.connection.on('read', this.onRead);

View File

@ -7,7 +7,7 @@
ref="text"
v-model="text"
:placeholder="$ts.inputMessageHere"
@keypress="onKeypress"
@keydown="onKeydown"
@compositionupdate="onCompositionUpdate"
@paste="onPaste"
></textarea>
@ -28,6 +28,7 @@ import * as autosize from 'autosize';
import { formatTimeString } from '@/scripts/format-time-string';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
import { stream } from '@/stream';
import { Autocomplete } from '@/scripts/autocomplete';
import { throttle } from 'throttle-debounce';
@ -48,7 +49,7 @@ export default defineComponent({
file: null,
sending: false,
typing: throttle(3000, () => {
os.stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id });
stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id });
}),
};
},
@ -140,7 +141,7 @@ export default defineComponent({
//#endregion
},
onKeypress(e) {
onKeydown(e) {
this.typing();
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) {
this.send();

View File

@ -24,7 +24,7 @@
</I18n>
<MkEllipsis/>
</div>
<transition name="fade">
<transition :name="$store.state.animation ? 'fade' : ''">
<div v-show="showIndicator" class="new-message">
<button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas fa-arrow-circle-down"></i>{{ $ts.newMessageExists }}</button>
</div>
@ -43,6 +43,7 @@ import XForm from './messaging-room.form.vue';
import * as Acct from 'misskey-js/built/acct';
import { isBottom, onScrollBottom, scroll } from '@/scripts/scroll';
import * as os from '@/os';
import { stream } from '@/stream';
import { popout } from '@/scripts/popout';
import * as sound from '@/scripts/sound';
import * as symbols from '@/symbols';
@ -141,7 +142,7 @@ const Component = defineComponent({
this.group = group;
}
this.connection = markRaw(os.stream.useChannel('messaging', {
this.connection = markRaw(stream.useChannel('messaging', {
otherparty: this.user ? this.user.id : undefined,
group: this.group ? this.group.id : undefined,
}));
@ -161,7 +162,7 @@ const Component = defineComponent({
// もっと見るの交差検知を発火させないためにfetchは
// スクロールが終わるまでfalseにしておく
// scrollendのようなイベントはないのでsetTimeoutで
setTimeout(() => this.fetching = false, 300);
window.setTimeout(() => this.fetching = false, 300);
});
},
@ -299,9 +300,9 @@ const Component = defineComponent({
this.showIndicator = false;
});
if (this.timer) clearTimeout(this.timer);
if (this.timer) window.clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.timer = window.setTimeout(() => {
this.showIndicator = false;
}, 4000);
},

View File

@ -4,45 +4,37 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
<script lang="ts" setup>
import { } from 'vue';
import XAntenna from './editor.vue';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
import { router } from '@/router';
export default defineComponent({
components: {
MkButton,
XAntenna,
let draft = $ref({
name: '',
src: 'all',
userListId: null,
userGroupId: null,
users: [],
keywords: [],
excludeKeywords: [],
withReplies: false,
caseSensitive: false,
withFile: false,
notify: false
});
function onAntennaCreated() {
router.push('/my/antennas');
}
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.manageAntennas,
icon: 'fas fa-satellite',
bg: 'var(--bg)',
},
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>

View File

@ -38,7 +38,7 @@ export default defineComponent({
}
},
pagination: {
endpoint: 'antennas/list',
endpoint: 'antennas/list' as const,
limit: 10,
},
};

View File

@ -3,7 +3,7 @@
<div class="qtcaoidl">
<MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
<MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="list">
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="list">
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
<b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div>
@ -13,71 +13,64 @@
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import MkButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import i18n from '@/components/global/i18n';
export default defineComponent({
components: {
MkPagination,
MkButton,
const pagination = {
endpoint: 'clips/list' as const,
limit: 10,
};
const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
async function create() {
const { canceled, result } = await os.form(i18n.locale.createNewClip, {
name: {
type: 'string',
label: i18n.locale.name,
},
description: {
type: 'string',
required: false,
multiline: true,
label: i18n.locale.description,
},
isPublic: {
type: 'boolean',
label: i18n.locale.public,
default: false,
},
});
if (canceled) return;
os.apiWithDialog('clips/create', result);
pagingComponent.reload();
}
function onClipCreated() {
pagingComponent.reload();
}
function onClipDeleted() {
pagingComponent.reload();
}
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.clip,
icon: 'fas fa-paperclip',
bg: 'var(--bg)',
action: {
icon: 'fas fa-plus',
handler: create
},
},
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.clip,
icon: 'fas fa-paperclip',
bg: 'var(--bg)',
action: {
icon: 'fas fa-plus',
handler: this.create
}
},
pagination: {
endpoint: 'clips/list',
limit: 10,
},
draft: null,
};
},
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>

View File

@ -1,6 +1,6 @@
<template>
<div class="mk-group-page">
<transition name="zoom" mode="out-in">
<transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<div v-if="group" class="_section">
<div class="_content" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
<MkButton inline @click="invite()">{{ $ts.invite }}</MkButton>
@ -11,7 +11,7 @@
</div>
</transition>
<transition name="zoom" mode="out-in">
<transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<div v-if="group" class="_section members _gap">
<div class="_title">{{ $ts.members }}</div>
<div class="_content">

View File

@ -87,15 +87,15 @@ export default defineComponent({
})),
tab: 'owned',
ownedPagination: {
endpoint: 'users/groups/owned',
endpoint: 'users/groups/owned' as const,
limit: 10,
},
joinedPagination: {
endpoint: 'users/groups/joined',
endpoint: 'users/groups/joined' as const,
limit: 10,
},
invitationPagination: {
endpoint: 'i/user-group-invites',
endpoint: 'i/user-group-invites' as const,
limit: 10,
},
};

View File

@ -3,7 +3,7 @@
<div class="qkcjvfiv">
<MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton>
<MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="lists _content">
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists _content">
<MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`">
<div class="name">{{ list.name }}</div>
<MkAvatars :user-ids="list.userIds"/>
@ -13,50 +13,41 @@
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import MkButton from '@/components/ui/button.vue';
import MkAvatars from '@/components/avatars.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
MkPagination,
MkButton,
MkAvatars,
},
const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.manageLists,
icon: 'fas fa-list-ul',
bg: 'var(--bg)',
action: {
icon: 'fas fa-plus',
handler: this.create
},
},
pagination: {
endpoint: 'users/lists/list',
limit: 10,
},
};
},
const pagination = {
endpoint: 'users/lists/list' as const,
limit: 10,
};
methods: {
async create() {
const { canceled, result: name } = await os.inputText({
title: this.$ts.enterListName,
});
if (canceled) return;
await os.api('users/lists/create', { name: name });
this.$refs.list.reload();
os.success();
async function create() {
const { canceled, result: name } = await os.inputText({
title: i18n.locale.enterListName,
});
if (canceled) return;
await os.apiWithDialog('users/lists/create', { name: name });
pagingComponent.reload();
}
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.manageLists,
icon: 'fas fa-list-ul',
bg: 'var(--bg)',
action: {
icon: 'fas fa-plus',
handler: create,
},
}
},
});
</script>

View File

@ -1,7 +1,7 @@
<template>
<MkSpacer :content-max="700">
<div class="mk-list-page">
<transition name="zoom" mode="out-in">
<transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<div v-if="list" class="_section">
<div class="_content">
<MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton>
@ -11,7 +11,7 @@
</div>
</transition>
<transition name="zoom" mode="out-in">
<transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<div v-if="list" class="_section members _gap">
<div class="_title">{{ $ts.members }}</div>
<div class="_content">

View File

@ -7,19 +7,15 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@/os';
<script lang="ts" setup>
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.notFound,
icon: 'fas fa-exclamation-triangle'
},
}
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.notFound,
icon: 'fas fa-exclamation-triangle',
bg: 'var(--bg)',
},
});
</script>

View File

@ -1,7 +1,7 @@
<template>
<MkSpacer :content-max="800">
<div class="fcuexfpr">
<transition name="fade" mode="out-in">
<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="note" class="note">
<div v-if="showNext" class="_gap">
<XNotes class="_content" :pagination="next" :no-gap="true"/>
@ -82,21 +82,21 @@ export default defineComponent({
showNext: false,
error: null,
prev: {
endpoint: 'users/notes',
endpoint: 'users/notes' as const,
limit: 10,
params: init => ({
params: computed(() => ({
userId: this.note.userId,
untilId: this.note.id,
})
})),
},
next: {
reversed: true,
endpoint: 'users/notes',
endpoint: 'users/notes' as const,
limit: 10,
params: init => ({
params: computed(() => ({
userId: this.note.userId,
sinceId: this.note.id,
})
})),
},
};
},

View File

@ -6,70 +6,62 @@
</MkSpacer>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script lang="ts" setup>
import { computed } from 'vue';
import XNotifications from '@/components/notifications.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { notificationTypes } from 'misskey-js';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
XNotifications
},
let tab = $ref('all');
let includeTypes = $ref<string[] | null>(null);
data() {
return {
[symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.notifications,
icon: 'fas fa-bell',
bg: 'var(--bg)',
actions: [{
text: this.$ts.filter,
icon: 'fas fa-filter',
highlighted: this.includeTypes != null,
handler: this.setFilter,
}, {
text: this.$ts.markAllAsRead,
icon: 'fas fa-check',
handler: () => {
os.apiWithDialog('notifications/mark-all-as-read');
},
}],
tabs: [{
active: this.tab === 'all',
title: this.$ts.all,
onClick: () => { this.tab = 'all'; },
}, {
active: this.tab === 'unread',
title: this.$ts.unread,
onClick: () => { this.tab = 'unread'; },
},]
})),
tab: 'all',
includeTypes: null,
};
},
methods: {
setFilter(ev) {
const typeItems = notificationTypes.map(t => ({
text: this.$t(`_notification._types.${t}`),
active: this.includeTypes && this.includeTypes.includes(t),
action: () => {
this.includeTypes = [t];
}
}));
const items = this.includeTypes != null ? [{
icon: 'fas fa-times',
text: this.$ts.clear,
action: () => {
this.includeTypes = null;
}
}, null, ...typeItems] : typeItems;
os.popupMenu(items, ev.currentTarget || ev.target);
function setFilter(ev) {
const typeItems = notificationTypes.map(t => ({
text: i18n.t(`_notification._types.${t}`),
active: includeTypes && includeTypes.includes(t),
action: () => {
includeTypes = [t];
}
}
}));
const items = includeTypes != null ? [{
icon: 'fas fa-times',
text: i18n.locale.clear,
action: () => {
includeTypes = null;
}
}, null, ...typeItems] : typeItems;
os.popupMenu(items, ev.currentTarget || ev.target);
}
defineExpose({
[symbols.PAGE_INFO]: computed(() => ({
title: i18n.locale.notifications,
icon: 'fas fa-bell',
bg: 'var(--bg)',
actions: [{
text: i18n.locale.filter,
icon: 'fas fa-filter',
highlighted: includeTypes != null,
handler: setFilter,
}, {
text: i18n.locale.markAllAsRead,
icon: 'fas fa-check',
handler: () => {
os.apiWithDialog('notifications/mark-all-as-read');
},
}],
tabs: [{
active: tab === 'all',
title: i18n.locale.all,
onClick: () => { tab = 'all'; },
}, {
active: tab === 'unread',
title: i18n.locale.unread,
onClick: () => { tab = 'unread'; },
},]
})),
});
</script>

View File

@ -1,6 +1,6 @@
<template>
<MkSpacer :content-max="700">
<transition name="fade" mode="out-in">
<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="page" :key="page.id" v-size="{ max: [450] }" class="xcukqgmh">
<div class="_block main">
<!--
@ -106,11 +106,11 @@ export default defineComponent({
page: null,
error: null,
otherPostsPagination: {
endpoint: 'users/pages',
endpoint: 'users/pages' as const,
limit: 6,
params: () => ({
params: computed(() => ({
userId: this.page.user.id
})
})),
},
};
},

View File

@ -62,15 +62,15 @@ export default defineComponent({
})),
tab: 'featured',
featuredPagesPagination: {
endpoint: 'pages/featured',
endpoint: 'pages/featured' as const,
noPaging: true,
},
myPagesPagination: {
endpoint: 'i/pages',
endpoint: 'i/pages' as const,
limit: 5,
},
likedPagesPagination: {
endpoint: 'i/page-likes',
endpoint: 'i/page-likes' as const,
limit: 5,
},
};

View File

@ -4,24 +4,18 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { computed } from 'vue';
import MkSample from '@/components/sample.vue';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
MkSample,
},
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.preview,
icon: 'fas fa-eye',
},
}
},
defineExpose({
[symbols.PAGE_INFO]: computed(() => ({
title: i18n.locale.preview,
icon: 'fas fa-eye',
bg: 'var(--bg)',
})),
});
</script>

View File

@ -1,67 +1,53 @@
<template>
<FormBase v-if="token">
<FormInput v-model="password" type="password">
<template #prefix><i class="fas fa-lock"></i></template>
<span>{{ $ts.newPassword }}</span>
</FormInput>
<FormButton primary @click="save">{{ $ts.save }}</FormButton>
</FormBase>
<MkSpacer v-if="token" :content-max="700" :margin-min="16" :margin-max="32">
<div class="_formRoot">
<FormInput v-model="password" type="password" class="_formBlock">
<template #prefix><i class="fas fa-lock"></i></template>
<template #label>{{ i18n.locale.newPassword }}</template>
</FormInput>
<FormButton primary class="_formBlock" @click="save">{{ i18n.locale.save }}</FormButton>
</div>
</MkSpacer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormButton from '@/components/debobigego/button.vue';
<script lang="ts" setup>
import { onMounted } from 'vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
import { router } from '@/router';
export default defineComponent({
components: {
FormBase,
FormGroup,
FormLink,
FormInput,
FormButton,
},
const props = defineProps<{
token?: string;
}>();
props: {
token: {
type: String,
required: false
}
},
let password = $ref('');
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.resetPassword,
icon: 'fas fa-lock'
},
password: '',
}
},
async function save() {
await os.apiWithDialog('reset-password', {
token: props.token,
password: password,
});
router.push('/');
}
mounted() {
if (this.token == null) {
os.popup(import('@/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('/');
}
onMounted(() => {
if (props.token == null) {
os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed');
router.push('/');
}
});
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.resetPassword,
icon: 'fas fa-lock',
bg: 'var(--bg)',
},
});
</script>
<style lang="scss" scoped>

View File

@ -1,528 +0,0 @@
<template>
<div class="xqnhankfuuilcwvhgsopeqncafzsquya">
<header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ $ts._reversi.black }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ $ts._reversi.white }})</header>
<div style="overflow: hidden; line-height: 28px;">
<p v-if="!iAmPlayer && !game.isEnded" class="turn">
<Mfm :key="'turn:' + turnUser().name" :text="$t('_reversi.turnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
<MkEllipsis/>
</p>
<p v-if="logPos != logs.length" class="turn">
<Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
</p>
<p v-if="iAmPlayer && !game.isEnded && !isMyTurn()" class="turn1">{{ $ts._reversi.opponentTurn }}<MkEllipsis/></p>
<p v-if="iAmPlayer && !game.isEnded && isMyTurn()" class="turn2" style="animation: tada 1s linear infinite both;">{{ $ts._reversi.myTurn }}</p>
<p v-if="game.isEnded && logPos == logs.length" class="result">
<template v-if="game.winner">
<Mfm :key="'won'" :text="$t('_reversi.won', { name: game.winner.name })" :plain="true" :custom-emojis="game.winner.emojis"/>
<span v-if="game.surrendered != null"> ({{ $ts._reversi.surrendered }})</span>
</template>
<template v-else>{{ $ts._reversi.drawn }}</template>
</p>
</div>
<div class="board">
<div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x">
<span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
</div>
<div class="flex">
<div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y">
<div v-for="i in game.map.length">{{ i }}</div>
</div>
<div class="cells" :style="cellsStyle">
<div v-for="(stone, i) in o.board"
:class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn(), can: turnUser() ? o.canPut(turnUser().id == blackUser.id, i) : null, prev: o.prevPos == i }"
:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`"
@click="set(i)"
>
<template v-if="$store.state.gamesReversiUseAvatarStones || true">
<img v-if="stone === true" :src="blackUser.avatarUrl" alt="black">
<img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white">
</template>
<template v-else>
<i v-if="stone === true" class="fas fa-circle"></i>
<i v-if="stone === false" class="far fa-circle"></i>
</template>
</div>
</div>
<div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y">
<div v-for="i in game.map.length">{{ i }}</div>
</div>
</div>
<div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x">
<span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
</div>
</div>
<p class="status"><b>{{ $t('_reversi.turnCount', { count: logPos }) }}</b> {{ $ts._reversi.black }}:{{ o.blackCount }} {{ $ts._reversi.white }}:{{ o.whiteCount }} {{ $ts._reversi.total }}:{{ o.blackCount + o.whiteCount }}</p>
<div v-if="!game.isEnded && iAmPlayer" class="actions">
<MkButton inline @click="surrender">{{ $ts._reversi.surrender }}</MkButton>
</div>
<div v-if="game.isEnded" class="player">
<span>{{ logPos }} / {{ logs.length }}</span>
<div v-if="!autoplaying" class="buttons">
<MkButton inline :disabled="logPos == 0" @click="logPos = 0"><i class="fas fa-angle-double-left"></i></MkButton>
<MkButton inline :disabled="logPos == 0" @click="logPos--"><i class="fas fa-angle-left"></i></MkButton>
<MkButton inline :disabled="logPos == logs.length" @click="logPos++"><i class="fas fa-angle-right"></i></MkButton>
<MkButton inline :disabled="logPos == logs.length" @click="logPos = logs.length"><i class="fas fa-angle-double-right"></i></MkButton>
</div>
<MkButton :disabled="autoplaying" style="margin: var(--margin) auto 0 auto;" @click="autoplay()"><i class="fas fa-play"></i></MkButton>
</div>
<div class="info">
<p v-if="game.isLlotheo">{{ $ts._reversi.isLlotheo }}</p>
<p v-if="game.loopedBoard">{{ $ts._reversi.loopedMap }}</p>
<p v-if="game.canPutEverywhere">{{ $ts._reversi.canPutEverywhere }}</p>
</div>
<div class="watchers">
<MkAvatar v-for="user in watchers" :key="user.id" :user="user" class="avatar"/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as CRC32 from 'crc-32';
import Reversi, { Color } from '@/scripts/games/reversi/core';
import { url } from '@/config';
import MkButton from '@/components/ui/button.vue';
import { userPage } from '@/filters/user';
import * as os from '@/os';
import * as sound from '@/scripts/sound';
export default defineComponent({
components: {
MkButton
},
props: {
initGame: {
type: Object,
require: true
},
connection: {
type: Object,
require: true
},
},
data() {
return {
game: JSON.parse(JSON.stringify(this.initGame)),
o: null as Reversi,
logs: [],
logPos: 0,
watchers: [],
pollingClock: null,
};
},
computed: {
iAmPlayer(): boolean {
if (!this.$i) return false;
return this.game.user1Id == this.$i.id || this.game.user2Id == this.$i.id;
},
myColor(): Color {
if (!this.iAmPlayer) return null;
if (this.game.user1Id == this.$i.id && this.game.black == 1) return true;
if (this.game.user2Id == this.$i.id && this.game.black == 2) return true;
return false;
},
opColor(): Color {
if (!this.iAmPlayer) return null;
return this.myColor === true ? false : true;
},
blackUser(): any {
return this.game.black == 1 ? this.game.user1 : this.game.user2;
},
whiteUser(): any {
return this.game.black == 1 ? this.game.user2 : this.game.user1;
},
cellsStyle(): any {
return {
'grid-template-rows': `repeat(${this.game.map.length}, 1fr)`,
'grid-template-columns': `repeat(${this.game.map[0].length}, 1fr)`
};
}
},
watch: {
logPos(v) {
if (!this.game.isEnded) return;
const o = new Reversi(this.game.map, {
isLlotheo: this.game.isLlotheo,
canPutEverywhere: this.game.canPutEverywhere,
loopedBoard: this.game.loopedBoard
});
for (const log of this.logs.slice(0, v)) {
o.put(log.color, log.pos);
}
this.o = o;
//this.$forceUpdate();
}
},
created() {
this.o = new Reversi(this.game.map, {
isLlotheo: this.game.isLlotheo,
canPutEverywhere: this.game.canPutEverywhere,
loopedBoard: this.game.loopedBoard
});
for (const log of this.game.logs) {
this.o.put(log.color, log.pos);
}
this.logs = this.game.logs;
this.logPos = this.logs.length;
// 通信を取りこぼしてもいいように定期的にポーリングさせる
if (this.game.isStarted && !this.game.isEnded) {
this.pollingClock = setInterval(() => {
if (this.game.isEnded) return;
const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join(''));
this.connection.send('check', {
crc32: crc32
});
}, 3000);
}
},
mounted() {
this.connection.on('set', this.onSet);
this.connection.on('rescue', this.onRescue);
this.connection.on('ended', this.onEnded);
this.connection.on('watchers', this.onWatchers);
},
beforeUnmount() {
this.connection.off('set', this.onSet);
this.connection.off('rescue', this.onRescue);
this.connection.off('ended', this.onEnded);
this.connection.off('watchers', this.onWatchers);
clearInterval(this.pollingClock);
},
methods: {
userPage,
// this.o がリアクティブになった折にはcomputedにできる
turnUser(): any {
if (this.o.turn === true) {
return this.game.black == 1 ? this.game.user1 : this.game.user2;
} else if (this.o.turn === false) {
return this.game.black == 1 ? this.game.user2 : this.game.user1;
} else {
return null;
}
},
// this.o がリアクティブになった折にはcomputedにできる
isMyTurn(): boolean {
if (!this.iAmPlayer) return false;
if (this.turnUser() == null) return false;
return this.turnUser().id == this.$i.id;
},
set(pos) {
if (this.game.isEnded) return;
if (!this.iAmPlayer) return;
if (!this.isMyTurn()) return;
if (!this.o.canPut(this.myColor, pos)) return;
this.o.put(this.myColor, pos);
// サウンドを再生する
sound.play(this.myColor ? 'reversiPutBlack' : 'reversiPutWhite');
this.connection.send('set', {
pos: pos
});
this.checkEnd();
this.$forceUpdate();
},
onSet(x) {
this.logs.push(x);
this.logPos++;
this.o.put(x.color, x.pos);
this.checkEnd();
this.$forceUpdate();
// サウンドを再生する
if (x.color !== this.myColor) {
sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite');
}
},
onEnded(x) {
this.game = JSON.parse(JSON.stringify(x.game));
},
checkEnd() {
this.game.isEnded = this.o.isEnded;
if (this.game.isEnded) {
if (this.o.winner === true) {
this.game.winnerId = this.game.black == 1 ? this.game.user1Id : this.game.user2Id;
this.game.winner = this.game.black == 1 ? this.game.user1 : this.game.user2;
} else if (this.o.winner === false) {
this.game.winnerId = this.game.black == 1 ? this.game.user2Id : this.game.user1Id;
this.game.winner = this.game.black == 1 ? this.game.user2 : this.game.user1;
} else {
this.game.winnerId = null;
this.game.winner = null;
}
}
},
// 正しいゲーム情報が送られてきたとき
onRescue(game) {
this.game = JSON.parse(JSON.stringify(game));
this.o = new Reversi(this.game.map, {
isLlotheo: this.game.isLlotheo,
canPutEverywhere: this.game.canPutEverywhere,
loopedBoard: this.game.loopedBoard
});
for (const log of this.game.logs) {
this.o.put(log.color, log.pos, true);
}
this.logs = this.game.logs;
this.logPos = this.logs.length;
this.checkEnd();
this.$forceUpdate();
},
onWatchers(users) {
this.watchers = users;
},
surrender() {
os.api('games/reversi/games/surrender', {
gameId: this.game.id
});
},
autoplay() {
this.autoplaying = true;
this.logPos = 0;
setTimeout(() => {
this.logPos = 1;
let i = 1;
let previousLog = this.game.logs[0];
const tick = () => {
const log = this.game.logs[i];
const time = new Date(log.at).getTime() - new Date(previousLog.at).getTime()
setTimeout(() => {
i++;
this.logPos++;
previousLog = log;
if (i < this.game.logs.length) {
tick();
} else {
this.autoplaying = false;
}
}, time);
};
tick();
}, 1000);
}
}
});
</script>
<style lang="scss" scoped>
@use "sass:math";
.xqnhankfuuilcwvhgsopeqncafzsquya {
text-align: center;
> .go-index {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 42px;
height :42px;
}
> header {
padding: 8px;
border-bottom: dashed 1px var(--divider);
}
> .board {
width: calc(100% - 16px);
max-width: 500px;
margin: 0 auto;
$label-size: 16px;
$gap: 4px;
> .labels-x {
height: $label-size;
padding: 0 $label-size;
display: flex;
> * {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8em;
&:first-child {
margin-left: -(math.div($gap, 2));
}
&:last-child {
margin-right: -(math.div($gap, 2));
}
}
}
> .flex {
display: flex;
> .labels-y {
width: $label-size;
display: flex;
flex-direction: column;
> * {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
&:first-child {
margin-top: -(math.div($gap, 2));
}
&:last-child {
margin-bottom: -(math.div($gap, 2));
}
}
}
> .cells {
flex: 1;
display: grid;
grid-gap: $gap;
> div {
background: transparent;
border-radius: 6px;
overflow: hidden;
* {
pointer-events: none;
user-select: none;
}
&.empty {
border: solid 2px var(--divider);
}
&.empty.can {
border-color: var(--accent);
}
&.empty.myTurn {
border-color: var(--divider);
&.can {
border-color: var(--accent);
cursor: pointer;
&:hover {
background: var(--accent);
}
}
}
&.prev {
box-shadow: 0 0 0 4px var(--accent);
}
&.isEnded {
border-color: var(--divider);
}
&.none {
border-color: transparent !important;
}
> svg, > img {
display: block;
width: 100%;
height: 100%;
}
}
}
}
}
> .status {
margin: 0;
padding: 16px 0;
}
> .actions {
padding-bottom: 16px;
}
> .player {
padding: 0 16px 32px 16px;
margin: 0 auto;
max-width: 500px;
> span {
display: inline-block;
margin: 0 8px;
min-width: 70px;
}
> .buttons {
display: flex;
> * {
flex: 1;
}
}
}
> .watchers {
padding: 0 0 16px 0;
&:empty {
display: none;
}
> .avatar {
width: 32px;
height: 32px;
}
}
}
</style>

View File

@ -1,390 +0,0 @@
<template>
<div class="urbixznjwwuukfsckrwzwsqzsxornqij">
<header><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></header>
<div>
<p>{{ $ts._reversi.gameSettings }}</p>
<div class="card map _panel">
<header>
<select v-model="mapName" :placeholder="$ts._reversi.chooseBoard" @change="onMapChange">
<option v-if="mapName == '-Custom-'" label="-Custom-" :value="mapName"/>
<option :label="$ts.random" :value="null"/>
<optgroup v-for="c in mapCategories" :key="c" :label="c">
<option v-for="m in Object.values(maps).filter(m => m.category == c)" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option>
</optgroup>
</select>
</header>
<div>
<div v-if="game.map == null" class="random"><i class="fas fa-dice"></i></div>
<div v-else class="board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
<div v-for="(x, i) in game.map.join('')" :class="{ none: x == ' ' }" @click="onPixelClick(i, x)">
<i v-if="x === 'b'" class="fas fa-circle"></i>
<i v-if="x === 'w'" class="far fa-circle"></i>
</div>
</div>
</div>
</div>
<div class="card _panel">
<header>
<span>{{ $ts._reversi.blackOrWhite }}</span>
</header>
<div>
<MkRadio v-model="game.bw" value="random" @update:modelValue="updateSettings('bw')">{{ $ts.random }}</MkRadio>
<MkRadio v-model="game.bw" :value="'1'" @update:modelValue="updateSettings('bw')">
<I18n :src="$ts._reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user1"/></b>
</template>
</I18n>
</MkRadio>
<MkRadio v-model="game.bw" :value="'2'" @update:modelValue="updateSettings('bw')">
<I18n :src="$ts._reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user2"/></b>
</template>
</I18n>
</MkRadio>
</div>
</div>
<div class="card _panel">
<header>
<span>{{ $ts._reversi.rules }}</span>
</header>
<div>
<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ $ts._reversi.isLlotheo }}</MkSwitch>
<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ $ts._reversi.loopedMap }}</MkSwitch>
<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ $ts._reversi.canPutEverywhere }}</MkSwitch>
</div>
</div>
<div v-if="form" class="card form _panel">
<header>
<span>{{ $ts._reversi.botSettings }}</span>
</header>
<div>
<template v-for="item in form">
<MkSwitch v-if="item.type == 'switch'" :key="item.id" v-model="item.value" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</MkSwitch>
<div v-if="item.type == 'radio'" :key="item.id" class="card">
<header>
<span>{{ item.label }}</span>
</header>
<div>
<MkRadio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :value="r.value" @update:modelValue="onChangeForm(item)">{{ r.label }}</MkRadio>
</div>
</div>
<div v-if="item.type == 'slider'" :key="item.id" class="card">
<header>
<span>{{ item.label }}</span>
</header>
<div>
<input v-model="item.value" type="range" :min="item.min" :max="item.max" :step="item.step || 1" @change="onChangeForm(item)"/>
</div>
</div>
<div v-if="item.type == 'textbox'" :key="item.id" class="card">
<header>
<span>{{ item.label }}</span>
</header>
<div>
<input v-model="item.value" @change="onChangeForm(item)"/>
</div>
</div>
</template>
</div>
</div>
</div>
<footer class="_acrylic">
<p class="status">
<template v-if="isAccepted && isOpAccepted">{{ $ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template>
<template v-if="isAccepted && !isOpAccepted">{{ $ts._reversi.waitingForOther }}<MkEllipsis/></template>
<template v-if="!isAccepted && isOpAccepted">{{ $ts._reversi.waitingForMe }}</template>
<template v-if="!isAccepted && !isOpAccepted">{{ $ts._reversi.waitingBoth }}<MkEllipsis/></template>
</p>
<div class="actions">
<MkButton inline @click="exit">{{ $ts.cancel }}</MkButton>
<MkButton v-if="!isAccepted" inline primary @click="accept">{{ $ts._reversi.ready }}</MkButton>
<MkButton v-if="isAccepted" inline primary @click="cancel">{{ $ts._reversi.cancelReady }}</MkButton>
</div>
</footer>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as maps from '@/scripts/games/reversi/maps';
import MkButton from '@/components/ui/button.vue';
import MkSwitch from '@/components/form/switch.vue';
import MkRadio from '@/components/form/radio.vue';
export default defineComponent({
components: {
MkButton,
MkSwitch,
MkRadio,
},
props: {
initGame: {
type: Object,
require: true
},
connection: {
type: Object,
require: true
},
},
data() {
return {
game: this.initGame,
o: null,
isLlotheo: false,
mapName: maps.eighteight.name,
maps: maps,
form: null,
messages: [],
};
},
computed: {
mapCategories(): string[] {
const categories = Object.values(maps).map(x => x.category);
return categories.filter((item, pos) => categories.indexOf(item) == pos);
},
isAccepted(): boolean {
if (this.game.user1Id == this.$i.id && this.game.user1Accepted) return true;
if (this.game.user2Id == this.$i.id && this.game.user2Accepted) return true;
return false;
},
isOpAccepted(): boolean {
if (this.game.user1Id != this.$i.id && this.game.user1Accepted) return true;
if (this.game.user2Id != this.$i.id && this.game.user2Accepted) return true;
return false;
}
},
created() {
this.connection.on('changeAccepts', this.onChangeAccepts);
this.connection.on('updateSettings', this.onUpdateSettings);
this.connection.on('initForm', this.onInitForm);
this.connection.on('message', this.onMessage);
if (this.game.user1Id != this.$i.id && this.game.form1) this.form = this.game.form1;
if (this.game.user2Id != this.$i.id && this.game.form2) this.form = this.game.form2;
},
beforeUnmount() {
this.connection.off('changeAccepts', this.onChangeAccepts);
this.connection.off('updateSettings', this.onUpdateSettings);
this.connection.off('initForm', this.onInitForm);
this.connection.off('message', this.onMessage);
},
methods: {
exit() {
},
accept() {
this.connection.send('accept', {});
},
cancel() {
this.connection.send('cancelAccept', {});
},
onChangeAccepts(accepts) {
this.game.user1Accepted = accepts.user1;
this.game.user2Accepted = accepts.user2;
},
updateSettings(key: string) {
this.connection.send('updateSettings', {
key: key,
value: this.game[key]
});
},
onUpdateSettings({ key, value }) {
this.game[key] = value;
if (this.game.map == null) {
this.mapName = null;
} else {
const found = Object.values(maps).find(x => x.data.join('') == this.game.map.join(''));
this.mapName = found ? found.name : '-Custom-';
}
},
onInitForm(x) {
if (x.userId == this.$i.id) return;
this.form = x.form;
},
onMessage(x) {
if (x.userId == this.$i.id) return;
this.messages.unshift(x.message);
},
onChangeForm(item) {
this.connection.send('updateForm', {
id: item.id,
value: item.value
});
},
onMapChange() {
if (this.mapName == null) {
this.game.map = null;
} else {
this.game.map = Object.values(maps).find(x => x.name == this.mapName).data;
}
this.updateSettings('map');
},
onPixelClick(pos, pixel) {
const x = pos % this.game.map[0].length;
const y = Math.floor(pos / this.game.map[0].length);
const newPixel =
pixel == ' ' ? '-' :
pixel == '-' ? 'b' :
pixel == 'b' ? 'w' :
' ';
const line = this.game.map[y].split('');
line[x] = newPixel;
this.game.map[y] = line.join('');
this.updateSettings('map');
}
}
});
</script>
<style lang="scss" scoped>
.urbixznjwwuukfsckrwzwsqzsxornqij {
text-align: center;
background: var(--bg);
> header {
padding: 8px;
border-bottom: dashed 1px #c4cdd4;
}
> div {
padding: 0 16px;
> .card {
margin: 0 auto 16px auto;
&.map {
> header {
> select {
width: 100%;
padding: 12px 14px;
background: var(--face);
border: 1px solid var(--inputBorder);
border-radius: 4px;
color: var(--fg);
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
&:focus-visible,
&:active {
border-color: var(--accent);
}
}
}
> div {
> .random {
padding: 32px 0;
font-size: 64px;
color: var(--fg);
opacity: 0.7;
}
> .board {
display: grid;
grid-gap: 4px;
width: 300px;
height: 300px;
margin: 0 auto;
color: var(--fg);
> div {
background: transparent;
border: solid 2px var(--divider);
border-radius: 6px;
overflow: hidden;
cursor: pointer;
* {
pointer-events: none;
user-select: none;
width: 100%;
height: 100%;
}
&.none {
border-color: transparent;
}
}
}
}
}
&.form {
> div {
> .card + .card {
margin-top: 16px;
}
input[type='range'] {
width: 100%;
}
}
}
}
.card {
max-width: 400px;
> header {
padding: 18px 20px;
border-bottom: 1px solid var(--divider);
}
> div {
padding: 20px;
color: var(--fg);
}
}
}
> footer {
position: sticky;
bottom: 0;
padding: 16px;
border-top: solid 1px var(--divider);
> .status {
margin: 0 0 16px 0;
}
}
}
</style>

View File

@ -1,76 +0,0 @@
<template>
<div v-if="game == null"><MkLoading/></div>
<GameSetting v-else-if="!game.isStarted" :init-game="game" :connection="connection"/>
<GameBoard v-else :init-game="game" :connection="connection"/>
</template>
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import GameSetting from './game.setting.vue';
import GameBoard from './game.board.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
GameSetting,
GameBoard,
},
props: {
gameId: {
type: String,
required: true
},
},
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts._reversi.reversi,
icon: 'fas fa-gamepad'
},
game: null,
connection: null,
};
},
watch: {
gameId() {
this.fetch();
}
},
mounted() {
this.fetch();
},
beforeUnmount() {
if (this.connection) {
this.connection.dispose();
}
},
methods: {
fetch() {
os.api('games/reversi/games/show', {
gameId: this.gameId
}).then(game => {
this.game = game;
if (this.connection) {
this.connection.dispose();
}
this.connection = markRaw(os.stream.useChannel('gamesReversiGame', {
gameId: this.game.id
}));
this.connection.on('started', this.onStarted);
});
},
onStarted(game) {
Object.assign(this.game, game);
},
}
});
</script>

View File

@ -1,279 +0,0 @@
<template>
<div v-if="!matching" class="bgvwxkhb">
<h1>Misskey {{ $ts._reversi.reversi }}</h1>
<div class="play">
<MkButton primary round style="margin: var(--margin) auto 0 auto;" @click="match">{{ $ts.invite }}</MkButton>
</div>
<div class="_section">
<MkFolder v-if="invitations.length > 0">
<template #header>{{ $ts.invitations }}</template>
<div class="nfcacttm">
<button v-for="invitation in invitations" class="invitation _panel _button" tabindex="-1" @click="accept(invitation)">
<MkAvatar class="avatar" :user="invitation.parent" :show-indicator="true"/>
<span class="name"><b><MkUserName :user="invitation.parent"/></b></span>
<span class="username">@{{ invitation.parent.username }}</span>
<MkTime :time="invitation.createdAt" class="time"/>
</button>
</div>
</MkFolder>
<MkFolder v-if="myGames.length > 0">
<template #header>{{ $ts._reversi.myGames }}</template>
<div class="knextgwz">
<MkA v-for="g in myGames" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`">
<div class="players">
<MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/>
</div>
<footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer>
</MkA>
</div>
</MkFolder>
<MkFolder v-if="games.length > 0">
<template #header>{{ $ts._reversi.allGames }}</template>
<div class="knextgwz">
<MkA v-for="g in games" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`">
<div class="players">
<MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/>
</div>
<footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer>
</MkA>
</div>
</MkFolder>
</div>
</div>
<div v-else class="sazhgisb">
<h1>
<I18n :src="$ts.waitingFor" tag="span">
<template #x>
<b><MkUserName :user="matching"/></b>
</template>
</I18n>
<MkEllipsis/>
</h1>
<div class="cancel">
<MkButton inline round @click="cancel">{{ $ts.cancel }}</MkButton>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import * as os from '@/os';
import MkButton from '@/components/ui/button.vue';
import MkFolder from '@/components/ui/folder.vue';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
MkButton, MkFolder,
},
inject: ['navHook'],
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts._reversi.reversi,
icon: 'fas fa-gamepad'
},
games: [],
gamesFetching: true,
gamesMoreFetching: false,
myGames: [],
matching: null,
invitations: [],
connection: null,
pingClock: null,
};
},
mounted() {
if (this.$i) {
this.connection = markRaw(os.stream.useChannel('gamesReversi'));
this.connection.on('invited', this.onInvited);
this.connection.on('matched', this.onMatched);
this.pingClock = setInterval(() => {
if (this.matching) {
this.connection.send('ping', {
id: this.matching.id
});
}
}, 3000);
os.api('games/reversi/games', {
my: true
}).then(games => {
this.myGames = games;
});
os.api('games/reversi/invitations').then(invitations => {
this.invitations = this.invitations.concat(invitations);
});
}
os.api('games/reversi/games').then(games => {
this.games = games;
this.gamesFetching = false;
});
},
beforeUnmount() {
if (this.connection) {
this.connection.dispose();
clearInterval(this.pingClock);
}
},
methods: {
go(game) {
const url = '/games/reversi/' + game.id;
if (this.navHook) {
this.navHook(url);
} else {
this.$router.push(url);
}
},
async match() {
const user = await os.selectUser({ local: true });
if (user == null) return;
os.api('games/reversi/match', {
userId: user.id
}).then(res => {
if (res == null) {
this.matching = user;
} else {
this.go(res);
}
});
},
cancel() {
this.matching = null;
os.api('games/reversi/match/cancel');
},
accept(invitation) {
os.api('games/reversi/match', {
userId: invitation.parent.id
}).then(game => {
if (game) {
this.go(game);
}
});
},
onMatched(game) {
this.go(game);
},
onInvited(invite) {
this.invitations.unshift(invite);
}
}
});
</script>
<style lang="scss" scoped>
.bgvwxkhb {
> h1 {
margin: 0;
padding: 24px;
text-align: center;
font-size: 1.5em;
background: linear-gradient(0deg, #43c583, #438881);
color: #fff;
}
> .play {
text-align: center;
}
}
.sazhgisb {
text-align: center;
}
.nfcacttm {
> .invitation {
display: flex;
box-sizing: border-box;
width: 100%;
padding: 16px;
line-height: 32px;
text-align: left;
> .avatar {
width: 32px;
height: 32px;
margin-right: 8px;
}
> .name {
margin-right: 8px;
}
> .username {
margin-right: 8px;
opacity: 0.7;
}
> .time {
margin-left: auto;
opacity: 0.7;
}
}
}
.knextgwz {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
grid-gap: var(--margin);
> .game {
> .players {
text-align: center;
padding: 16px;
line-height: 32px;
> .avatar {
width: 32px;
height: 32px;
&:first-child {
margin-right: 8px;
}
&:last-child {
margin-left: 8px;
}
}
}
> footer {
display: flex;
align-items: baseline;
border-top: solid 0.5px var(--divider);
padding: 6px 8px;
font-size: 0.9em;
> .state {
&.playing {
color: var(--accent);
}
}
> .time {
margin-left: auto;
opacity: 0.7;
}
}
}
}
</style>

View File

@ -1,107 +0,0 @@
<template>
<canvas width="224" height="128"></canvas>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as THREE from 'three';
import * as os from '@/os';
export default defineComponent({
data() {
return {
selected: null,
objectHeight: 0,
orbitRadius: 5
};
},
mounted() {
const canvas = this.$el;
const width = canvas.width;
const height = canvas.height;
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
renderer.setClearColor(0x000000);
renderer.autoClear = false;
renderer.shadowMap.enabled = true;
renderer.shadowMap.cullFace = THREE.CullFaceBack;
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
camera.zoom = 10;
camera.position.x = 0;
camera.position.y = 2;
camera.position.z = 0;
camera.updateProjectionMatrix();
scene.add(camera);
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
ambientLight.castShadow = false;
scene.add(ambientLight);
const light = new THREE.PointLight(0xffffff, 1, 100);
light.position.set(3, 3, 3);
scene.add(light);
const grid = new THREE.GridHelper(5, 16, 0x444444, 0x222222);
scene.add(grid);
const render = () => {
const timer = Date.now() * 0.0004;
requestAnimationFrame(render);
camera.position.y = Math.sin(Math.PI / 6) * this.orbitRadius; // Math.PI / 6 => 30deg
camera.position.z = Math.cos(timer) * this.orbitRadius;
camera.position.x = Math.sin(timer) * this.orbitRadius;
camera.lookAt(new THREE.Vector3(0, this.objectHeight / 2, 0));
renderer.render(scene, camera);
};
this.selected = selected => {
const obj = selected.clone();
// Remove current object
const current = scene.getObjectByName('obj');
if (current != null) {
scene.remove(current);
}
// Add new object
obj.name = 'obj';
obj.position.x = 0;
obj.position.y = 0;
obj.position.z = 0;
obj.rotation.x = 0;
obj.rotation.y = 0;
obj.rotation.z = 0;
obj.traverse(child => {
if (child instanceof THREE.Mesh) {
child.material = child.material.clone();
return child.material.emissive.setHex(0x000000);
}
});
const objectBoundingBox = new THREE.Box3().setFromObject(obj);
this.objectHeight = objectBoundingBox.max.y - objectBoundingBox.min.y;
const objectWidth = objectBoundingBox.max.x - objectBoundingBox.min.x;
const objectDepth = objectBoundingBox.max.z - objectBoundingBox.min.z;
const horizontal = Math.hypot(objectWidth, objectDepth) / camera.aspect;
this.orbitRadius = Math.max(horizontal, this.objectHeight) * camera.zoom * 0.625 / Math.tan(camera.fov * 0.5 * (Math.PI / 180));
scene.add(obj);
};
render();
},
});
</script>

View File

@ -1,279 +0,0 @@
<template>
<div class="hveuntkp">
<div v-if="objectSelected" class="controller _section">
<div class="_content">
<p class="name">{{ selectedFurnitureName }}</p>
<XPreview ref="preview"/>
<template v-if="selectedFurnitureInfo.props">
<div v-for="k in Object.keys(selectedFurnitureInfo.props)" :key="k">
<p>{{ k }}</p>
<template v-if="selectedFurnitureInfo.props[k] === 'image'">
<MkButton @click="chooseImage(k, $event)">{{ $ts._rooms.chooseImage }}</MkButton>
</template>
<template v-else-if="selectedFurnitureInfo.props[k] === 'color'">
<input type="color" :value="selectedFurnitureProps ? selectedFurnitureProps[k] : null" @change="updateColor(k, $event)"/>
</template>
</div>
</template>
</div>
<div class="_content">
<MkButton inline :primary="isTranslateMode" @click="translate()"><i class="fas fa-arrows-alt"></i> {{ $ts._rooms.translate }}</MkButton>
<MkButton inline :primary="isRotateMode" @click="rotate()"><i class="fas fa-undo"></i> {{ $ts._rooms.rotate }}</MkButton>
<MkButton v-if="isTranslateMode || isRotateMode" inline @click="exit()"><i class="fas fa-ban"></i> {{ $ts._rooms.exit }}</MkButton>
</div>
<div class="_content">
<MkButton @click="remove()"><i class="fas fa-trash-alt"></i> {{ $ts._rooms.remove }}</MkButton>
</div>
</div>
<div v-if="isMyRoom" class="menu _section">
<div class="_content">
<MkButton @click="add()"><i class="fas fa-box-open"></i> {{ $ts._rooms.addFurniture }}</MkButton>
</div>
<div class="_content">
<MkSelect :model-value="roomType" @update:modelValue="updateRoomType($event)">
<template #label>{{ $ts._rooms.roomType }}</template>
<option value="default">{{ $ts._rooms._roomType.default }}</option>
<option value="washitsu">{{ $ts._rooms._roomType.washitsu }}</option>
</MkSelect>
<label v-if="roomType === 'default'">
<span>{{ $ts._rooms.carpetColor }}</span>
<input type="color" :value="carpetColor" @change="updateCarpetColor($event)"/>
</label>
</div>
<div class="_content">
<MkButton inline :disabled="!changed" primary @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
<MkButton inline @click="clear()"><i class="fas fa-broom"></i> {{ $ts._rooms.clear }}</MkButton>
</div>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { Room } from '@/scripts/room/room';
import * as Acct from 'misskey-js/built/acct';
import XPreview from './preview.vue';
const storeItems = require('@/scripts/room/furnitures.json5');
import { query as urlQuery } from '@/scripts/url';
import MkButton from '@/components/ui/button.vue';
import MkSelect from '@/components/form/select.vue';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import * as symbols from '@/symbols';
let room: Room;
export default defineComponent({
components: {
XPreview,
MkButton,
MkSelect,
},
beforeRouteLeave(to, from, next) {
if (this.changed) {
os.confirm({
type: 'warning',
text: this.$ts.leaveConfirm,
}).then(({ canceled }) => {
if (canceled) {
next(false);
} else {
next();
}
});
} else {
next();
}
},
props: {
acct: {
type: String,
required: true
},
},
data() {
return {
[symbols.PAGE_INFO]: computed(() => this.user ? {
title: this.$ts.room,
avatar: this.user,
} : null),
user: null,
objectSelected: false,
selectedFurnitureName: null,
selectedFurnitureInfo: null,
selectedFurnitureProps: null,
roomType: null,
carpetColor: null,
isTranslateMode: false,
isRotateMode: false,
isMyRoom: false,
changed: false,
};
},
async mounted() {
window.addEventListener('beforeunload', this.beforeunload);
this.user = await os.api('users/show', {
...Acct.parse(this.acct)
});
this.isMyRoom = this.$i && (this.$i.id === this.user.id);
const roomInfo = await os.api('room/show', {
userId: this.user.id
});
this.roomType = roomInfo.roomType;
this.carpetColor = roomInfo.carpetColor;
room = new Room(this.user, this.isMyRoom, roomInfo, this.$el, {
graphicsQuality: ColdDeviceStorage.get('roomGraphicsQuality'),
onChangeSelect: obj => {
this.objectSelected = obj != null;
if (obj) {
const f = room.findFurnitureById(obj.name);
this.selectedFurnitureName = this.$t('_rooms._furnitures.' + f.type);
this.selectedFurnitureInfo = storeItems.find(x => x.id === f.type);
this.selectedFurnitureProps = f.props
? JSON.parse(JSON.stringify(f.props)) // Disable reactivity
: null;
this.$nextTick(() => {
this.$refs.preview.selected(obj);
});
}
},
useOrthographicCamera: ColdDeviceStorage.get('roomUseOrthographicCamera'),
});
},
beforeUnmount() {
room.destroy();
window.removeEventListener('beforeunload', this.beforeunload);
},
methods: {
beforeunload(e: BeforeUnloadEvent) {
if (this.changed) {
e.preventDefault();
e.returnValue = '';
}
},
async add() {
const { canceled, result: id } = await os.select({
title: this.$ts._rooms.addFurniture,
items: storeItems.map(item => ({
value: item.id, text: this.$t('_rooms._furnitures.' + item.id)
}))
});
if (canceled) return;
room.addFurniture(id);
this.changed = true;
},
remove() {
this.isTranslateMode = false;
this.isRotateMode = false;
room.removeFurniture();
this.changed = true;
},
save() {
os.api('room/update', {
room: room.getRoomInfo()
}).then(() => {
this.changed = false;
os.success();
}).catch((e: any) => {
os.alert({
type: 'error',
text: e.message
});
});
},
clear() {
os.confirm({
type: 'warning',
text: this.$ts._rooms.clearConfirm,
}).then(({ canceled }) => {
if (canceled) return;
room.removeAllFurnitures();
this.changed = true;
});
},
chooseImage(key, e) {
selectFile(e.currentTarget || e.target, null).then(file => {
room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`);
this.$refs.preview.selected(room.getSelectedObject());
this.changed = true;
});
},
updateColor(key, ev) {
room.updateProp(key, ev.target.value);
this.$refs.preview.selected(room.getSelectedObject());
this.changed = true;
},
updateCarpetColor(ev) {
room.updateCarpetColor(ev.target.value);
this.carpetColor = ev.target.value;
this.changed = true;
},
updateRoomType(type) {
room.changeRoomType(type);
this.roomType = type;
this.changed = true;
},
translate() {
if (this.isTranslateMode) {
this.exit();
} else {
this.isRotateMode = false;
this.isTranslateMode = true;
room.enterTransformMode('translate');
}
this.changed = true;
},
rotate() {
if (this.isRotateMode) {
this.exit();
} else {
this.isTranslateMode = false;
this.isRotateMode = true;
room.enterTransformMode('rotate');
}
this.changed = true;
},
exit() {
this.isTranslateMode = false;
this.isRotateMode = false;
room.exitTransformMode();
this.changed = true;
}
}
});
</script>
<style lang="scss" scoped>
.hveuntkp {
position: relative;
min-height: 500px;
> ::v-deep(canvas) {
display: block;
}
}
</style>

View File

@ -6,37 +6,31 @@
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script lang="ts" setup>
import { computed } from 'vue';
import XNotes from '@/components/notes.vue';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
XNotes
},
const props = defineProps<{
query: string;
channel?: string;
}>();
data() {
return {
[symbols.PAGE_INFO]: {
title: computed(() => this.$t('searchWith', { q: this.$route.query.q })),
icon: 'fas fa-search',
},
pagination: {
endpoint: 'notes/search',
limit: 10,
params: () => ({
query: this.$route.query.q,
channelId: this.$route.query.channel,
})
},
};
},
const pagination = {
endpoint: 'notes/search' as const,
limit: 10,
params: computed(() => ({
query: props.query,
channelId: props.channel,
}))
};
watch: {
$route() {
(this.$refs.notes as any).reload();
}
},
defineExpose({
[symbols.PAGE_INFO]: computed(() => ({
title: i18n.t('searchWith', { q: props.query }),
icon: 'fas fa-search',
bg: 'var(--bg)',
})),
});
</script>

View File

@ -71,9 +71,6 @@ import MkButton from '@/components/ui/button.vue';
import MkInfo from '@/components/ui/info.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormButton from '@/components/debobigego/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';

View File

@ -1,144 +1,135 @@
<template>
<FormBase>
<FormKeyValueView>
<div class="_formRoot">
<MkKeyValue>
<template #key>ID</template>
<template #value><span class="_monospace">{{ $i.id }}</span></template>
</FormKeyValueView>
</MkKeyValue>
<FormGroup>
<FormKeyValueView>
<FormSection>
<MkKeyValue>
<template #key>{{ $ts.registeredDate }}</template>
<template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
</FormKeyValueView>
</FormGroup>
</MkKeyValue>
</FormSection>
<FormGroup v-if="stats">
<FormSection v-if="stats">
<template #label>{{ $ts.statistics }}</template>
<FormKeyValueView>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.notesCount }}</template>
<template #value>{{ number(stats.notesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.repliesCount }}</template>
<template #value>{{ number(stats.repliesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.renotesCount }}</template>
<template #value>{{ number(stats.renotesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.repliedCount }}</template>
<template #value>{{ number(stats.repliedCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.renotedCount }}</template>
<template #value>{{ number(stats.renotedCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.pollVotesCount }}</template>
<template #value>{{ number(stats.pollVotesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.pollVotedCount }}</template>
<template #value>{{ number(stats.pollVotedCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.sentReactionsCount }}</template>
<template #value>{{ number(stats.sentReactionsCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.receivedReactionsCount }}</template>
<template #value>{{ number(stats.receivedReactionsCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.noteFavoritesCount }}</template>
<template #value>{{ number(stats.noteFavoritesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followingCount }}</template>
<template #value>{{ number(stats.followingCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template>
<template #value>{{ number(stats.localFollowingCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template>
<template #value>{{ number(stats.remoteFollowingCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followersCount }}</template>
<template #value>{{ number(stats.followersCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template>
<template #value>{{ number(stats.localFollowersCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template>
<template #value>{{ number(stats.remoteFollowersCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.pageLikesCount }}</template>
<template #value>{{ number(stats.pageLikesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.pageLikedCount }}</template>
<template #value>{{ number(stats.pageLikedCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.driveFilesCount }}</template>
<template #value>{{ number(stats.driveFilesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.driveUsage }}</template>
<template #value>{{ bytes(stats.driveUsage) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $ts.reversiCount }}</template>
<template #value>{{ number(stats.reversiCount) }}</template>
</FormKeyValueView>
</FormGroup>
</MkKeyValue>
</FormSection>
<FormGroup>
<FormSection>
<template #label>{{ $ts.other }}</template>
<FormKeyValueView>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>emailVerified</template>
<template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>twoFactorEnabled</template>
<template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>securityKeys</template>
<template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>usePasswordLessLogin</template>
<template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>isModerator</template>
<template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template>
</FormKeyValueView>
<FormKeyValueView>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>isAdmin</template>
<template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template>
</FormKeyValueView>
</FormGroup>
</FormBase>
</MkKeyValue>
</FormSection>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/key-value.vue';
import * as os from '@/os';
import number from '@/filters/number';
import bytes from '@/filters/bytes';
@ -146,13 +137,8 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormSelect,
FormSwitch,
FormButton,
FormLink,
FormGroup,
FormKeyValueView,
FormSection,
MkKeyValue,
},
emits: ['info'],
@ -168,8 +154,6 @@ export default defineComponent({
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
os.api('users/stats', {
userId: this.$i.id
}).then(stats => {

View File

@ -1,41 +1,35 @@
<template>
<FormBase>
<div class="_formRoot">
<FormSuspense :p="init">
<FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton>
<div v-for="account in accounts" :key="account.id" class="_debobigegoItem _button" @click="menu(account, $event)">
<div class="_debobigegoPanel lcjjdxlm">
<div class="avatar">
<MkAvatar :user="account" class="avatar"/>
<div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)">
<div class="avatar">
<MkAvatar :user="account" class="avatar"/>
</div>
<div class="body">
<div class="name">
<MkUserName :user="account"/>
</div>
<div class="body">
<div class="name">
<MkUserName :user="account"/>
</div>
<div class="acct">
<MkAcct :user="account"/>
</div>
<div class="acct">
<MkAcct :user="account"/>
</div>
</div>
</div>
</FormSuspense>
</FormBase>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSuspense from '@/components/debobigego/suspense.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { getAccounts, addAccount, login } from '@/account';
export default defineComponent({
components: {
FormBase,
FormSuspense,
FormButton,
},
@ -59,10 +53,6 @@ export default defineComponent({
};
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
menu(account, ev) {
os.popupMenu([{

View File

@ -1,25 +1,20 @@
<template>
<FormBase>
<FormButton primary @click="generateToken">{{ $ts.generateAccessToken }}</FormButton>
<FormLink to="/settings/apps">{{ $ts.manageAccessTokens }}</FormLink>
<FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink>
</FormBase>
<div class="_formRoot">
<FormButton primary class="_formBlock" @click="generateToken">{{ $ts.generateAccessToken }}</FormButton>
<FormLink to="/settings/apps" class="_formBlock">{{ $ts.manageAccessTokens }}</FormLink>
<FormLink to="/api-console" :behavior="isDesktop ? 'window' : null" class="_formBlock">API console</FormLink>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormLink from '@/components/form/link.vue';
import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormButton,
FormLink,
},
@ -37,10 +32,6 @@ export default defineComponent({
};
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
generateToken() {
os.popup(import('@/components/token-generate-window.vue'), {}, {

View File

@ -1,5 +1,5 @@
<template>
<FormBase>
<div class="_formRoot">
<FormPagination ref="list" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
@ -8,7 +8,7 @@
</div>
</template>
<template v-slot="{items}">
<div v-for="token in items" :key="token.id" class="_debobigegoPanel bfomjevm">
<div v-for="token in items" :key="token.id" class="_panel bfomjevm">
<img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/>
<div class="body">
<div class="name">{{ token.name }}</div>
@ -34,23 +34,17 @@
</div>
</template>
</FormPagination>
</FormBase>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormPagination from '@/components/debobigego/pagination.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormPagination from '@/components/ui/pagination.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormPagination,
},
@ -64,7 +58,7 @@ export default defineComponent({
bg: 'var(--bg)',
},
pagination: {
endpoint: 'i/apps',
endpoint: 'i/apps' as const,
limit: 100,
params: {
sort: '+lastUsedAt'
@ -73,10 +67,6 @@ export default defineComponent({
};
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
revoke(token) {
os.api('i/revoke-token', { tokenId: token.id }).then(() => {

View File

@ -1,25 +1,18 @@
<template>
<FormBase>
<FormInfo warn>{{ $ts.customCssWarn }}</FormInfo>
<div class="_formRoot">
<FormInfo warn class="_formBlock">{{ $ts.customCssWarn }}</FormInfo>
<FormTextarea v-model="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;">
<span>{{ $ts.local }}</span>
<FormTextarea v-model="localCustomCss" manual-save tall class="_monospace _formBlock" style="tab-size: 2;">
<template #label>CSS</template>
</FormTextarea>
</FormBase>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormSelect from '@/components/form/select.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormInfo from '@/components/ui/info.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
import * as symbols from '@/symbols';
import { defaultStore } from '@/store';
@ -27,12 +20,6 @@ import { defaultStore } from '@/store';
export default defineComponent({
components: {
FormTextarea,
FormSelect,
FormRadios,
FormBase,
FormGroup,
FormLink,
FormButton,
FormInfo,
},
@ -50,8 +37,6 @@ export default defineComponent({
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
this.$watch('localCustomCss', this.apply);
},

View File

@ -1,42 +1,41 @@
<template>
<FormBase>
<div class="_formRoot">
<FormGroup>
<template #label>{{ $ts.defaultNavigationBehaviour }}</template>
<FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch>
</FormGroup>
<FormSwitch v-model="alwaysShowMainColumn">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch>
<FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch>
<FormRadios v-model="columnAlign">
<template #desc>{{ $ts._deck.columnAlign }}</template>
<FormRadios v-model="columnAlign" class="_formBlock">
<template #label>{{ $ts._deck.columnAlign }}</template>
<option value="left">{{ $ts.left }}</option>
<option value="center">{{ $ts.center }}</option>
</FormRadios>
<FormRadios v-model="columnHeaderHeight">
<template #desc>{{ $ts._deck.columnHeaderHeight }}</template>
<FormRadios v-model="columnHeaderHeight" class="_formBlock">
<template #label>{{ $ts._deck.columnHeaderHeight }}</template>
<option :value="42">{{ $ts.narrow }}</option>
<option :value="45">{{ $ts.medium }}</option>
<option :value="48">{{ $ts.wide }}</option>
</FormRadios>
<FormInput v-model="columnMargin" type="number">
<span>{{ $ts._deck.columnMargin }}</span>
<FormInput v-model="columnMargin" type="number" class="_formBlock">
<template #label>{{ $ts._deck.columnMargin }}</template>
<template #suffix>px</template>
</FormInput>
<FormLink @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
</FormBase>
<FormLink class="_formBlock" @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormSwitch from '@/components/debobigego/switch.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormRadios from '@/components/debobigego/radios.vue';
import FormInput from '@/components/debobigego/input.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormLink from '@/components/form/link.vue';
import FormRadios from '@/components/form/radios.vue';
import FormInput from '@/components/form/input.vue';
import FormGroup from '@/components/form/group.vue';
import { deckStore } from '@/ui/deck/deck-store';
import * as os from '@/os';
import { unisonReload } from '@/scripts/unison-reload';
@ -48,7 +47,6 @@ export default defineComponent({
FormLink,
FormInput,
FormRadios,
FormBase,
FormGroup,
},
@ -85,10 +83,6 @@ export default defineComponent({
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async setProfile() {
const { canceled, result: name } = await os.inputText({

View File

@ -1,28 +1,23 @@
<template>
<FormBase>
<FormInfo warn>{{ $ts._accountDelete.mayTakeTime }}</FormInfo>
<FormInfo>{{ $ts._accountDelete.sendEmail }}</FormInfo>
<FormButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton>
<div class="_formRoot">
<FormInfo warn class="_formBlock">{{ $ts._accountDelete.mayTakeTime }}</FormInfo>
<FormInfo class="_formBlock">{{ $ts._accountDelete.sendEmail }}</FormInfo>
<FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton>
<FormButton v-else disabled>{{ $ts._accountDelete.inProgress }}</FormButton>
</FormBase>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormInfo from '@/components/ui/info.vue';
import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import { debug } from '@/config';
import { signout } from '@/account';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormButton,
FormGroup,
FormInfo,
},
@ -35,14 +30,9 @@ export default defineComponent({
icon: 'fas fa-exclamation-triangle',
bg: 'var(--bg)',
},
debug,
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async deleteAccount() {
{

View File

@ -5,7 +5,7 @@
<div class="_formBlock uawsfosz">
<div class="meter"><div :style="meterStyle"></div></div>
</div>
<div class="_inputSplit _formBlock">
<FormSplit>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.capacity }}</template>
<template #value>{{ bytes(capacity, 1) }}</template>
@ -14,7 +14,7 @@
<template #key>{{ $ts.inUse }}</template>
<template #value>{{ bytes(usage, 1) }}</template>
</MkKeyValue>
</div>
</FormSplit>
</FormSection>
<FormSection>
@ -38,6 +38,7 @@ import * as tinycolor from 'tinycolor2';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/key-value.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import bytes from '@/filters/bytes';
import * as symbols from '@/symbols';
@ -49,6 +50,7 @@ export default defineComponent({
FormLink,
FormSection,
MkKeyValue,
FormSplit,
},
emits: ['info'],
@ -97,10 +99,6 @@ export default defineComponent({
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
chooseUploadFolder() {
os.selectDriveFolder(false).then(async folder => {

View File

@ -41,8 +41,6 @@
<script lang="ts">
import { defineComponent, onMounted, ref, watch } from 'vue';
import FormButton from '@/components/debobigego/button.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormSection from '@/components/form/section.vue';
import FormInput from '@/components/form/input.vue';
import FormSwitch from '@/components/form/switch.vue';
@ -54,8 +52,6 @@ import { i18n } from '@/i18n';
export default defineComponent({
components: {
FormSection,
FormLink,
FormButton,
FormSwitch,
FormInput,
},
@ -115,8 +111,6 @@ export default defineComponent({
});
onMounted(() => {
context.emit('info', INFO);
watch(emailAddress, () => {
saveEmailAddress();
});

View File

@ -1,52 +0,0 @@
<template>
<FormBase>
<FormButton @click="error()">error test</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormSelect,
FormSwitch,
FormButton,
FormLink,
FormGroup,
FormKeyValueView,
},
emits: ['info'],
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.experimentalFeatures,
icon: 'fas fa-flask'
},
stats: null
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
error() {
throw new Error('Test error');
}
}
});
</script>

View File

@ -195,10 +195,6 @@ export default defineComponent({
},
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async reloadAsk() {
const { canceled } = await os.confirm({

View File

@ -133,10 +133,6 @@ export default defineComponent({
os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
};
onMounted(() => {
context.emit('info', INFO);
});
return {
[symbols.PAGE_INFO]: INFO,
excludeMutingUsers,

View File

@ -14,7 +14,7 @@
</div>
<div class="main">
<div class="bkzroven">
<component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/>
<component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/>
</div>
</div>
</div>
@ -215,19 +215,9 @@ export default defineComponent({
case 'deck': return defineAsyncComponent(() => import('./deck.vue'));
case 'plugin': return defineAsyncComponent(() => import('./plugin.vue'));
case 'plugin/install': return defineAsyncComponent(() => import('./plugin.install.vue'));
case 'plugin/manage': return defineAsyncComponent(() => import('./plugin.manage.vue'));
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
case 'account-info': return defineAsyncComponent(() => import('./account-info.vue'));
case 'update': return defineAsyncComponent(() => import('./update.vue'));
case 'registry': return defineAsyncComponent(() => import('./registry.vue'));
case 'delete-account': return defineAsyncComponent(() => import('./delete-account.vue'));
case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue'));
}
if (page.value.startsWith('registry/keys/system/')) {
return defineAsyncComponent(() => import('./registry.keys.vue'));
}
if (page.value.startsWith('registry/value/system/')) {
return defineAsyncComponent(() => import('./registry.value.vue'));
}
return null;
});
@ -235,17 +225,6 @@ export default defineComponent({
watch(component, () => {
pageProps.value = {};
if (page.value) {
if (page.value.startsWith('registry/keys/system/')) {
pageProps.value.scope = page.value.replace('registry/keys/system/', '').split('/');
}
if (page.value.startsWith('registry/value/system/')) {
const path = page.value.replace('registry/value/system/', '').split('/');
pageProps.value.xKey = path.pop();
pageProps.value.scope = path;
}
}
nextTick(() => {
scroll(el.value, { top: 0 });
});
@ -271,8 +250,9 @@ export default defineComponent({
const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
const onInfo = (info) => {
childInfo.value = info;
const pageChanged = (page) => {
if (page == null) return;
childInfo.value = page[symbols.PAGE_INFO];
};
return {
@ -285,7 +265,7 @@ export default defineComponent({
pageProps,
component,
emailNotConfigured,
onInfo,
pageChanged,
childInfo,
};
},

View File

@ -47,11 +47,6 @@ export default defineComponent({
},
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
async created() {
this.instanceMutes = this.$i.mutedInstances.join('\n');
},

View File

@ -1,45 +1,39 @@
<template>
<FormBase>
<div v-if="enableTwitterIntegration" class="_debobigegoItem">
<div class="_debobigegoLabel"><i class="fab fa-twitter"></i> Twitter</div>
<div class="_debobigegoPanel" style="padding: 16px;">
<p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
<MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton>
</div>
</div>
<div class="_formRoot">
<FormSection v-if="enableTwitterIntegration">
<template #label><i class="fab fa-twitter"></i> Twitter</template>
<p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
<MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton>
</FormSection>
<div v-if="enableDiscordIntegration" class="_debobigegoItem">
<div class="_debobigegoLabel"><i class="fab fa-discord"></i> Discord</div>
<div class="_debobigegoPanel" style="padding: 16px;">
<p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p>
<MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton>
</div>
</div>
<FormSection v-if="enableDiscordIntegration">
<template #label><i class="fab fa-discord"></i> Discord</template>
<p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p>
<MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton>
</FormSection>
<div v-if="enableGithubIntegration" class="_debobigegoItem">
<div class="_debobigegoLabel"><i class="fab fa-github"></i> GitHub</div>
<div class="_debobigegoPanel" style="padding: 16px;">
<p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p>
<MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton>
</div>
</div>
</FormBase>
<FormSection v-if="enableGithubIntegration">
<template #label><i class="fab fa-github"></i> GitHub</template>
<p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p>
<MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton>
</FormSection>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { apiUrl } from '@/config';
import FormBase from '@/components/debobigego/base.vue';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/ui/button.vue';
import * as os from '@/os';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormSection,
MkButton
},
@ -79,8 +73,6 @@ export default defineComponent({
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
document.cookie = `igi=${this.$i.token}; path=/;` +
` max-age=31536000;` +
(document.location.protocol.startsWith('https') ? ' secure' : '');

View File

@ -21,7 +21,6 @@
import { defineComponent } from 'vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormButton from '@/components/ui/button.vue';
import * as os from '@/os';
import { menuDef } from '@/menu';
@ -31,7 +30,6 @@ import { unisonReload } from '@/scripts/unison-reload';
export default defineComponent({
components: {
FormBase,
FormButton,
FormTextarea,
FormRadios,
@ -69,10 +67,6 @@ export default defineComponent({
},
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
async addItem() {
const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k));

View File

@ -1,5 +1,5 @@
<template>
<FormBase>
<div class="_formRoot">
<MkTab v-model="tab" style="margin-bottom: var(--margin);">
<option value="mute">{{ $ts.mutedUsers }}</option>
<option value="block">{{ $ts.blockedUsers }}</option>
@ -8,11 +8,9 @@
<MkPagination :pagination="mutingPagination" class="muting">
<template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
<template v-slot="{items}">
<FormGroup>
<FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)">
<MkAcct :user="mute.mutee"/>
</FormLink>
</FormGroup>
<FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)">
<MkAcct :user="mute.mutee"/>
</FormLink>
</template>
</MkPagination>
</div>
@ -20,66 +18,43 @@
<MkPagination :pagination="blockingPagination" class="blocking">
<template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
<template v-slot="{items}">
<FormGroup>
<FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)">
<MkAcct :user="block.blockee"/>
</FormLink>
</FormGroup>
<FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)">
<MkAcct :user="block.blockee"/>
</FormLink>
</template>
</MkPagination>
</div>
</FormBase>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { } from 'vue';
import MkPagination from '@/components/ui/pagination.vue';
import MkTab from '@/components/tab.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormInfo from '@/components/ui/info.vue';
import FormLink from '@/components/form/link.vue';
import { userPage } from '@/filters/user';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
export default defineComponent({
components: {
MkPagination,
MkTab,
FormInfo,
FormBase,
FormGroup,
FormLink,
let tab = $ref('mute');
const mutingPagination = {
endpoint: 'mute/list' as const,
limit: 10,
};
const blockingPagination = {
endpoint: 'blocking/list' as const,
limit: 10,
};
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.muteAndBlock,
icon: 'fas fa-ban',
bg: 'var(--bg)',
},
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>

View File

@ -13,7 +13,6 @@
import { defineComponent } from 'vue';
import FormButton from '@/components/ui/button.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormSection from '@/components/form/section.vue';
import { notificationTypes } from 'misskey-js';
import * as os from '@/os';
@ -21,7 +20,6 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormLink,
FormButton,
FormSection,
@ -39,10 +37,6 @@ export default defineComponent({
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
readAllUnreadNotes() {
os.api('i/read-all-unread-notes');

View File

@ -1,30 +1,12 @@
<template>
<div class="_formRoot">
<FormLink to="/settings/update" class="_formBlock">Misskey Update</FormLink>
<FormSwitch :value="$i.injectFeaturedNote" @update:modelValue="onChangeInjectFeaturedNote" class="_formBlock">
<FormSwitch :value="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote">
{{ $ts.showFeaturedNotesInTimeline }}
</FormSwitch>
<FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #desc>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch>
<FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #caption>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch>
<FormLink to="/settings/account-info" class="_formBlock">{{ $ts.accountInfo }}</FormLink>
<FormLink to="/settings/experimental-features" class="_formBlock">{{ $ts.experimentalFeatures }}</FormLink>
<FormSection>
<template #label>{{ $ts.developer }}</template>
<FormSwitch v-model="debug" @update:modelValue="changeDebug" class="_formBlock">
DEBUG MODE
</FormSwitch>
<template v-if="debug">
<FormButton @click="taskmanager">Task Manager</FormButton>
</template>
</FormSection>
<FormLink to="/settings/registry" class="_formBlock"><template #icon><i class="fas fa-cogs"></i></template>{{ $ts.registry }}</FormLink>
<FormLink to="/bios" behavior="browser" class="_formBlock"><template #icon><i class="fas fa-door-open"></i></template>BIOS</FormLink>
<FormLink to="/cli" behavior="browser" class="_formBlock"><template #icon><i class="fas fa-door-open"></i></template>CLI</FormLink>
<FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink>
</div>
@ -33,10 +15,8 @@
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormLink from '@/components/form/link.vue';
import * as os from '@/os';
import { debug } from '@/config';
import { defaultStore } from '@/store';
@ -45,10 +25,8 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormSelect,
FormSection,
FormSwitch,
FormButton,
FormLink,
},
@ -69,10 +47,6 @@ export default defineComponent({
reportError: defaultStore.makeGetterSetter('reportError'),
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
changeDebug(v) {
console.log(v);
@ -85,11 +59,6 @@ export default defineComponent({
injectFeaturedNote: v
});
},
taskmanager() {
os.popup(import('@/components/taskmanager.vue'), {
}, {}, 'closed');
},
}
});
</script>

View File

@ -1,15 +1,15 @@
<template>
<FormBase>
<FormInfo warn>{{ $ts._plugin.installWarn }}</FormInfo>
<div class="_formRoot">
<FormInfo warn class="_formBlock">{{ $ts._plugin.installWarn }}</FormInfo>
<FormGroup>
<FormTextarea v-model="code" tall>
<span>{{ $ts.code }}</span>
</FormTextarea>
</FormGroup>
<FormTextarea v-model="code" tall class="_formBlock">
<template #label>{{ $ts.code }}</template>
</FormTextarea>
<FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton>
</FormBase>
<div class="_formBlock">
<FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton>
</div>
</div>
</template>
<script lang="ts">
@ -18,13 +18,8 @@ import { AiScript, parse } from '@syuilo/aiscript';
import { serialize } from '@syuilo/aiscript/built/serializer';
import { v4 as uuid } from 'uuid';
import FormTextarea from '@/components/form/textarea.vue';
import FormSelect from '@/components/form/select.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormButton from '@/components/debobigego/button.vue';
import FormInfo from '@/components/debobigego/info.vue';
import FormButton from '@/components/ui/button.vue';
import FormInfo from '@/components/ui/info.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
@ -33,11 +28,6 @@ import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormTextarea,
FormSelect,
FormRadios,
FormBase,
FormGroup,
FormLink,
FormButton,
FormInfo,
},
@ -55,10 +45,6 @@ export default defineComponent({
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
installPlugin({ id, meta, ast, token }) {
ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({

View File

@ -1,116 +0,0 @@
<template>
<FormBase>
<FormGroup v-for="plugin in plugins" :key="plugin.id">
<template #label><span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span></template>
<FormSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch>
<div class="_debobigegoItem">
<div class="_debobigegoPanel" style="padding: 16px;">
<div class="_keyValue">
<div>{{ $ts.author }}:</div>
<div>{{ plugin.author }}</div>
</div>
<div class="_keyValue">
<div>{{ $ts.description }}:</div>
<div>{{ plugin.description }}</div>
</div>
<div class="_keyValue">
<div>{{ $ts.permission }}:</div>
<div>{{ plugin.permissions }}</div>
</div>
</div>
</div>
<div class="_debobigegoItem">
<div class="_debobigegoPanel" style="padding: 16px;">
<MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton>
<MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton>
</div>
</div>
</FormGroup>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkSelect from '@/components/form/select.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import * as symbols from '@/symbols';
import { unisonReload } from '@/scripts/unison-reload';
export default defineComponent({
components: {
MkButton,
MkTextarea,
MkSelect,
FormSwitch,
FormBase,
FormGroup,
},
emits: ['info'],
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts._plugin.manage,
icon: 'fas fa-plug',
bg: 'var(--bg)',
},
plugins: ColdDeviceStorage.get('plugins'),
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
uninstall(plugin) {
ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id));
os.success();
this.$nextTick(() => {
unisonReload();
});
},
// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする
async config(plugin) {
const config = plugin.config;
for (const key in plugin.configData) {
config[key].default = plugin.configData[key];
}
const { canceled, result } = await os.form(plugin.name, config);
if (canceled) return;
const plugins = ColdDeviceStorage.get('plugins');
plugins.find(p => p.id === plugin.id).configData = result;
ColdDeviceStorage.set('plugins', plugins);
this.$nextTick(() => {
location.reload();
});
},
changeActive(plugin, active) {
const plugins = ColdDeviceStorage.get('plugins');
plugins.find(p => p.id === plugin.id).active = active;
ColdDeviceStorage.set('plugins', plugins);
this.$nextTick(() => {
location.reload();
});
}
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,23 +1,54 @@
<template>
<FormBase>
<div class="_formRoot">
<FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._plugin.install }}</FormLink>
<FormLink to="/settings/plugin/manage"><template #icon><i class="fas fa-folder-open"></i></template>{{ $ts._plugin.manage }}<template #suffix>{{ plugins }}</template></FormLink>
</FormBase>
<FormSection>
<template #label>{{ $ts.manage }}</template>
<div v-for="plugin in plugins" :key="plugin.id" class="_formBlock _panel" style="padding: 20px;">
<span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
<FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.author }}</template>
<template #value>{{ plugin.author }}</template>
</MkKeyValue>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.description }}</template>
<template #value>{{ plugin.description }}</template>
</MkKeyValue>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.permission }}</template>
<template #value>{{ plugin.permission }}</template>
</MkKeyValue>
<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
<MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton>
<MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton>
</div>
</div>
</FormSection>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue';
import FormLink from '@/components/debobigego/link.vue';
import FormLink from '@/components/form/link.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/ui/button.vue';
import MkKeyValue from '@/components/key-value.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import * as symbols from '@/symbols';
export default defineComponent({
components: {
FormBase,
FormLink,
FormSwitch,
FormSection,
MkButton,
MkKeyValue,
},
emits: ['info'],
@ -29,12 +60,47 @@ export default defineComponent({
icon: 'fas fa-plug',
bg: 'var(--bg)',
},
plugins: ColdDeviceStorage.get('plugins').length,
plugins: ColdDeviceStorage.get('plugins'),
}
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
methods: {
uninstall(plugin) {
ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id));
os.success();
this.$nextTick(() => {
unisonReload();
});
},
// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする
async config(plugin) {
const config = plugin.config;
for (const key in plugin.configData) {
config[key].default = plugin.configData[key];
}
const { canceled, result } = await os.form(plugin.name, config);
if (canceled) return;
const plugins = ColdDeviceStorage.get('plugins');
plugins.find(p => p.id === plugin.id).configData = result;
ColdDeviceStorage.set('plugins', plugins);
this.$nextTick(() => {
location.reload();
});
},
changeActive(plugin, active) {
const plugins = ColdDeviceStorage.get('plugins');
plugins.find(p => p.id === plugin.id).active = active;
ColdDeviceStorage.set('plugins', plugins);
this.$nextTick(() => {
location.reload();
});
}
},
});
</script>

View File

@ -47,8 +47,8 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormSection from '@/components/form/section.vue';
@ -56,67 +56,39 @@ import FormGroup from '@/components/form/group.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
import { $i } from '@/account';
export default defineComponent({
components: {
FormSelect,
FormSection,
FormGroup,
FormSwitch,
let isLocked = $ref($i.isLocked);
let autoAcceptFollowed = $ref($i.autoAcceptFollowed);
let noCrawle = $ref($i.noCrawle);
let isExplorable = $ref($i.isExplorable);
let hideOnlineStatus = $ref($i.hideOnlineStatus);
let publicReactions = $ref($i.publicReactions);
let ffVisibility = $ref($i.ffVisibility);
let defaultNoteVisibility = $computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
let defaultNoteLocalOnly = $computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
let rememberNoteVisibility = $computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
let keepCw = $computed(defaultStore.makeGetterSetter('keepCw'));
function save() {
os.api('i/update', {
isLocked: !!isLocked,
autoAcceptFollowed: !!autoAcceptFollowed,
noCrawle: !!noCrawle,
isExplorable: !!isExplorable,
hideOnlineStatus: !!hideOnlineStatus,
publicReactions: !!publicReactions,
ffVisibility: ffVisibility,
});
}
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.privacy,
icon: 'fas fa-lock-open',
bg: 'var(--bg)',
},
emits: ['info'],
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.privacy,
icon: 'fas fa-lock-open',
bg: 'var(--bg)',
},
isLocked: false,
autoAcceptFollowed: false,
noCrawle: false,
isExplorable: false,
hideOnlineStatus: false,
publicReactions: false,
ffVisibility: 'public',
}
},
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;
this.ffVisibility = this.$i.ffVisibility;
},
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,
ffVisibility: this.ffVisibility,
});
}
}
});
</script>

View File

@ -3,50 +3,50 @@
<div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
<div class="avatar _acrylic">
<MkAvatar class="avatar" :user="$i" :disable-link="true" @click="changeAvatar"/>
<MkButton primary class="avatarEdit" @click="changeAvatar">{{ $ts._profile.changeAvatar }}</MkButton>
<MkButton primary class="avatarEdit" @click="changeAvatar">{{ i18n.locale._profile.changeAvatar }}</MkButton>
</div>
<MkButton primary class="bannerEdit" @click="changeBanner">{{ $ts._profile.changeBanner }}</MkButton>
<MkButton primary class="bannerEdit" @click="changeBanner">{{ i18n.locale._profile.changeBanner }}</MkButton>
</div>
<FormInput v-model="name" :max="30" manual-save class="_formBlock">
<template #label>{{ $ts._profile.name }}</template>
<FormInput v-model="profile.name" :max="30" manual-save class="_formBlock">
<template #label>{{ i18n.locale._profile.name }}</template>
</FormInput>
<FormTextarea v-model="description" :max="500" tall manual-save class="_formBlock">
<template #label>{{ $ts._profile.description }}</template>
<template #caption>{{ $ts._profile.youCanIncludeHashtags }}</template>
<FormTextarea v-model="profile.description" :max="500" tall manual-save class="_formBlock">
<template #label>{{ i18n.locale._profile.description }}</template>
<template #caption>{{ i18n.locale._profile.youCanIncludeHashtags }}</template>
</FormTextarea>
<FormInput v-model="location" manual-save class="_formBlock">
<template #label>{{ $ts.location }}</template>
<FormInput v-model="profile.location" manual-save class="_formBlock">
<template #label>{{ i18n.locale.location }}</template>
<template #prefix><i class="fas fa-map-marker-alt"></i></template>
</FormInput>
<FormInput v-model="birthday" type="date" manual-save class="_formBlock">
<template #label>{{ $ts.birthday }}</template>
<FormInput v-model="profile.birthday" type="date" manual-save class="_formBlock">
<template #label>{{ i18n.locale.birthday }}</template>
<template #prefix><i class="fas fa-birthday-cake"></i></template>
</FormInput>
<FormSelect v-model="lang" class="_formBlock">
<template #label>{{ $ts.language }}</template>
<FormSelect v-model="profile.lang" class="_formBlock">
<template #label>{{ i18n.locale.language }}</template>
<option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option>
</FormSelect>
<FormSlot>
<MkButton @click="editMetadata">{{ $ts._profile.metadataEdit }}</MkButton>
<template #caption>{{ $ts._profile.metadataDescription }}</template>
<MkButton @click="editMetadata">{{ i18n.locale._profile.metadataEdit }}</MkButton>
<template #caption>{{ i18n.locale._profile.metadataDescription }}</template>
</FormSlot>
<FormSwitch v-model="isCat" class="_formBlock">{{ $ts.flagAsCat }}<template #caption>{{ $ts.flagAsCatDescription }}</template></FormSwitch>
<FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.locale.flagAsCat }}<template #caption>{{ i18n.locale.flagAsCatDescription }}</template></FormSwitch>
<FormSwitch v-model="isBot" class="_formBlock">{{ $ts.flagAsBot }}<template #caption>{{ $ts.flagAsBotDescription }}</template></FormSwitch>
<FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.locale.flagAsBot }}<template #caption>{{ i18n.locale.flagAsBotDescription }}</template></FormSwitch>
<FormSwitch v-model="alwaysMarkNsfw" class="_formBlock">{{ $ts.alwaysMarkSensitive }}</FormSwitch>
<FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.locale.alwaysMarkSensitive }}</FormSwitch>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { defineComponent, reactive, watch } from 'vue';
import MkButton from '@/components/ui/button.vue';
import FormInput from '@/components/form/input.vue';
import FormTextarea from '@/components/form/textarea.vue';
@ -57,198 +57,149 @@ import { host, langs } from '@/config';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
import { $i } from '@/account';
export default defineComponent({
components: {
MkButton,
FormInput,
FormTextarea,
FormSwitch,
FormSelect,
FormSlot,
},
emits: ['info'],
const profile = reactive({
name: $i.name,
description: $i.description,
location: $i.location,
birthday: $i.birthday,
lang: $i.lang,
isBot: $i.isBot,
isCat: $i.isCat,
alwaysMarkNsfw: $i.alwaysMarkNsfw,
});
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.profile,
icon: 'fas fa-user',
bg: 'var(--bg)',
},
host,
langs,
name: null,
description: null,
birthday: null,
lang: null,
location: null,
fieldName0: null,
fieldValue0: null,
fieldName1: null,
fieldValue1: null,
fieldName2: null,
fieldValue2: null,
fieldName3: null,
fieldValue3: null,
avatarId: null,
bannerId: null,
isBot: false,
isCat: false,
alwaysMarkNsfw: false,
saving: false,
}
},
const additionalFields = reactive({
fieldName0: $i.fields[0] ? $i.fields[0].name : null,
fieldValue0: $i.fields[0] ? $i.fields[0].value : null,
fieldName1: $i.fields[1] ? $i.fields[1].name : null,
fieldValue1: $i.fields[1] ? $i.fields[1].value : null,
fieldName2: $i.fields[2] ? $i.fields[2].name : null,
fieldValue2: $i.fields[2] ? $i.fields[2].value : null,
fieldName3: $i.fields[3] ? $i.fields[3].name : null,
fieldValue3: $i.fields[3] ? $i.fields[3].value : null,
});
created() {
this.name = this.$i.name;
this.description = this.$i.description;
this.location = this.$i.location;
this.birthday = this.$i.birthday;
this.lang = this.$i.lang;
this.avatarId = this.$i.avatarId;
this.bannerId = this.$i.bannerId;
this.isBot = this.$i.isBot;
this.isCat = this.$i.isCat;
this.alwaysMarkNsfw = this.$i.alwaysMarkNsfw;
watch(() => profile, () => {
save();
}, {
deep: true,
});
this.fieldName0 = this.$i.fields[0] ? this.$i.fields[0].name : null;
this.fieldValue0 = this.$i.fields[0] ? this.$i.fields[0].value : null;
this.fieldName1 = this.$i.fields[1] ? this.$i.fields[1].name : null;
this.fieldValue1 = this.$i.fields[1] ? this.$i.fields[1].value : null;
this.fieldName2 = this.$i.fields[2] ? this.$i.fields[2].name : null;
this.fieldValue2 = this.$i.fields[2] ? this.$i.fields[2].value : null;
this.fieldName3 = this.$i.fields[3] ? this.$i.fields[3].name : null;
this.fieldValue3 = this.$i.fields[3] ? this.$i.fields[3].value : null;
function save() {
os.apiWithDialog('i/update', {
name: profile.name || null,
description: profile.description || null,
location: profile.location || null,
birthday: profile.birthday || null,
lang: profile.lang || null,
isBot: !!profile.isBot,
isCat: !!profile.isCat,
alwaysMarkNsfw: !!profile.alwaysMarkNsfw,
});
}
this.$watch('name', this.save);
this.$watch('description', this.save);
this.$watch('location', this.save);
this.$watch('birthday', this.save);
this.$watch('lang', this.save);
this.$watch('isBot', this.save);
this.$watch('isCat', this.save);
this.$watch('alwaysMarkNsfw', this.save);
},
function changeAvatar(ev) {
selectFile(ev.currentTarget || ev.target, i18n.locale.avatar).then(async (file) => {
const i = await os.apiWithDialog('i/update', {
avatarId: file.id,
});
$i.avatarId = i.avatarId;
$i.avatarUrl = i.avatarUrl;
});
}
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
function changeBanner(ev) {
selectFile(ev.currentTarget || ev.target, i18n.locale.banner).then(async (file) => {
const i = await os.apiWithDialog('i/update', {
bannerId: file.id,
});
$i.bannerId = i.bannerId;
$i.bannerUrl = i.bannerUrl;
});
}
methods: {
changeAvatar(e) {
selectFile(e.currentTarget || e.target, this.$ts.avatar).then(file => {
os.api('i/update', {
avatarId: file.id,
});
});
async function editMetadata() {
const { canceled, result } = await os.form(i18n.locale._profile.metadata, {
fieldName0: {
type: 'string',
label: i18n.locale._profile.metadataLabel + ' 1',
default: additionalFields.fieldName0,
},
changeBanner(e) {
selectFile(e.currentTarget || e.target, this.$ts.banner).then(file => {
os.api('i/update', {
bannerId: file.id,
});
});
fieldValue0: {
type: 'string',
label: i18n.locale._profile.metadataContent + ' 1',
default: additionalFields.fieldValue0,
},
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.alert({
type: 'error',
text: err.id
});
});
fieldName1: {
type: 'string',
label: i18n.locale._profile.metadataLabel + ' 2',
default: additionalFields.fieldName1,
},
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;
});
fieldValue1: {
type: 'string',
label: i18n.locale._profile.metadataContent + ' 2',
default: additionalFields.fieldValue1,
},
}
fieldName2: {
type: 'string',
label: i18n.locale._profile.metadataLabel + ' 3',
default: additionalFields.fieldName2,
},
fieldValue2: {
type: 'string',
label: i18n.locale._profile.metadataContent + ' 3',
default: additionalFields.fieldValue2,
},
fieldName3: {
type: 'string',
label: i18n.locale._profile.metadataLabel + ' 4',
default: additionalFields.fieldName3,
},
fieldValue3: {
type: 'string',
label: i18n.locale._profile.metadataContent + ' 4',
default: additionalFields.fieldValue3,
},
});
if (canceled) return;
additionalFields.fieldName0 = result.fieldName0;
additionalFields.fieldValue0 = result.fieldValue0;
additionalFields.fieldName1 = result.fieldName1;
additionalFields.fieldValue1 = result.fieldValue1;
additionalFields.fieldName2 = result.fieldName2;
additionalFields.fieldValue2 = result.fieldValue2;
additionalFields.fieldName3 = result.fieldName3;
additionalFields.fieldValue3 = result.fieldValue3;
const fields = [
{ name: additionalFields.fieldName0, value: additionalFields.fieldValue0 },
{ name: additionalFields.fieldName1, value: additionalFields.fieldValue1 },
{ name: additionalFields.fieldName2, value: additionalFields.fieldValue2 },
{ name: additionalFields.fieldName3, value: additionalFields.fieldValue3 },
];
os.api('i/update', {
fields,
}).then(i => {
os.success();
}).catch(err => {
os.alert({
type: 'error',
text: err.id
});
});
}
defineExpose({
[symbols.PAGE_INFO]: {
title: i18n.locale.profile,
icon: 'fas fa-user',
bg: 'var(--bg)',
},
});
</script>

Some files were not shown because too many files have changed in this diff Show More