feat(frontend): 設定の検索 (#15505)
* wip * wip * wip * test * wip rollup pluginでsearchIndexの情報生成 * wip * SPDX * wip: markerIdを自動付与 * rollupでビルド時・devモード時に毎回uuidを生成するように * 開発サーバーでだけ必要な挙動は開発サーバーのみで * 条件が逆 * wip: childrenの生成 * update comment * update comment * rename auto generated file * hashをパスと行数から決定 * Update privacy.vue * Update privacy.vue * wip * Update general.vue * Update general.vue * wip * wip * Update SearchMarker.vue * wip * Update profile.vue * Update mute-block.vue * Update mute-block.vue * Update general.vue * Update general.vue * childrenがduplicate key errorを吐く問題をいったん解決 * マーカーの形を成形 * loggerを置きかえ * とりあえず省略記法に対応 * Refactor and Format codes * wip * Update settings-search-index.ts * wip * wip * とりあえず不確定要因の仮置きidを削除 * hashの生成を正規化(絶対パスになっていたのを緩和) * pathの入力を省略可能に * adminでもパス生成できるように * Update settings-search-index.ts * Update privacy.vue * wip * build searchIndex * wip * build * Update general.vue * build * Update sounds.vue * build * build * Update sounds.vue * 🎨 * 🎨 * Update privacy.vue * Update privacy.vue * Update security.vue * create-search-indexを多少改善 * build * Update 2fa.vue * wip * 必ずtransformCodeCacheを利用するように, キャッシュの明確な受け渡しを定義 * キャッシュはdevServerでなくても更新 * Revert "wip" This reverts commit 41bffd3a13f55618bf939dc1c9acb2a77ead4054. * inlining * wip * Update theme.vue * 🎨 * wip normalize * Update theme.vue * キャッシュのパス変換 * build * wip * wip * Update SearchMarker.vue * i18n.ts['key'] の形式が取り出せない問題のFix * build * 仮でpath入れ * 必ず絶対パスが使われるように * wip * 🎨 * storybookビルド時はcreateSearchIndexをしない * inliningの構造化 * format code * Update index.vue * wip * wip * 🎨 * wip * wip * wip * wip * wip * wip * wip * wip * clean up * Update navbar.vue * enhance: 検索で上下矢印を使用することで検索結果を移動できるように * refactor * fix(frontend): PageWindowでSearchMarkerが動作するように * enhance(frontend): SearchMarkerの点滅を一定時間で止める * lint fix * fix: 子要素監視が抜けていたのを修正 * アニメーションの回数はCSSで制御するように * refactor * enhance(frontend): 検索インデックス作成時のログを削減 * revert * fix * fix --------- Co-authored-by: tai-cha <dev@taichan.site> Co-authored-by: taichan <40626578+tai-cha@users.noreply.github.com> Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
This commit is contained in:
parent
46067f6e17
commit
0214a0001f
10
locales/index.d.ts
vendored
10
locales/index.d.ts
vendored
@ -4971,7 +4971,7 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"disableStreamingTimeline": string;
|
||||
/**
|
||||
* 通知をグルーピングして表示する
|
||||
* 通知をグルーピング
|
||||
*/
|
||||
"useGroupedNotifications": string;
|
||||
/**
|
||||
@ -5270,6 +5270,14 @@ export interface Locale extends ILocale {
|
||||
* このメディアのセンシティブ指定を解除しますか?
|
||||
*/
|
||||
"unmarkAsSensitiveConfirm": string;
|
||||
/**
|
||||
* 環境設定
|
||||
*/
|
||||
"preferences": string;
|
||||
/**
|
||||
* アクセシビリティ
|
||||
*/
|
||||
"accessibility": string;
|
||||
"_accountSettings": {
|
||||
/**
|
||||
* コンテンツの表示にログインを必須にする
|
||||
|
@ -1238,7 +1238,7 @@ releaseToRefresh: "離してリロード"
|
||||
refreshing: "リロード中"
|
||||
pullDownToRefresh: "引っ張ってリロード"
|
||||
disableStreamingTimeline: "タイムラインのリアルタイム更新を無効にする"
|
||||
useGroupedNotifications: "通知をグルーピングして表示する"
|
||||
useGroupedNotifications: "通知をグルーピング"
|
||||
signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。"
|
||||
cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。"
|
||||
doReaction: "リアクションする"
|
||||
@ -1313,6 +1313,8 @@ confirmOnReact: "リアクションする際に確認する"
|
||||
reactAreYouSure: "\" {emoji} \" をリアクションしますか?"
|
||||
markAsSensitiveConfirm: "このメディアをセンシティブとして設定しますか?"
|
||||
unmarkAsSensitiveConfirm: "このメディアのセンシティブ指定を解除しますか?"
|
||||
preferences: "環境設定"
|
||||
accessibility: "アクセシビリティ"
|
||||
|
||||
_accountSettings:
|
||||
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
||||
|
@ -39,6 +39,10 @@ const config = {
|
||||
if (~replacePluginForIsChromatic) {
|
||||
config.plugins?.splice(replacePluginForIsChromatic, 1);
|
||||
}
|
||||
|
||||
//pluginsからcreateSearchIndexを削除、複数あるかもしれないので全て削除
|
||||
config.plugins = config.plugins?.filter((plugin: Plugin) => plugin && plugin.name !== 'createSearchIndex') ?? [];
|
||||
|
||||
return mergeConfig(config, {
|
||||
plugins: [
|
||||
{
|
||||
|
1496
packages/frontend/lib/vite-plugin-create-search-index.ts
Normal file
1496
packages/frontend/lib/vite-plugin-create-search-index.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -51,6 +51,7 @@
|
||||
"insert-text-at-cursor": "0.3.0",
|
||||
"is-file-animated": "1.0.2",
|
||||
"json5": "2.2.3",
|
||||
"magic-string": "0.30.17",
|
||||
"matter-js": "0.20.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
|
@ -34,7 +34,7 @@ defineProps<{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: not-allowed;
|
||||
--color: color(from var(--MI_THEME-error) srgb r g b / 0.25);
|
||||
--color: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
|
||||
background-size: auto auto;
|
||||
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
|
||||
}
|
@ -91,6 +91,14 @@ const buttonsRight = computed(() => {
|
||||
});
|
||||
const reloadCount = ref(0);
|
||||
|
||||
function getSearchMarker(path: string) {
|
||||
const hash = path.split('#')[1];
|
||||
if (hash == null) return null;
|
||||
return hash;
|
||||
}
|
||||
|
||||
const searchMarkerId = ref<string | null>(getSearchMarker(props.initialPath));
|
||||
|
||||
windowRouter.addListener('push', ctx => {
|
||||
history.value.push({ path: ctx.path, key: ctx.key });
|
||||
});
|
||||
@ -101,7 +109,8 @@ windowRouter.addListener('replace', ctx => {
|
||||
});
|
||||
|
||||
windowRouter.addListener('change', ctx => {
|
||||
console.log('windowRouter: change', ctx.path);
|
||||
if (_DEV_) console.log('windowRouter: change', ctx.path);
|
||||
searchMarkerId.value = getSearchMarker(ctx.path);
|
||||
analytics.page({
|
||||
path: ctx.path,
|
||||
title: ctx.path,
|
||||
@ -111,6 +120,7 @@ windowRouter.addListener('change', ctx => {
|
||||
windowRouter.init();
|
||||
|
||||
provide('router', windowRouter);
|
||||
provide('inAppSearchMarkerId', searchMarkerId);
|
||||
provideMetadataReceiver((metadataGetter) => {
|
||||
const info = metadataGetter();
|
||||
pageMetadata.value = info;
|
||||
|
@ -4,27 +4,60 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="rrevdjwu" :class="{ grid }">
|
||||
<div v-for="group in def" class="group">
|
||||
<div v-if="group.title" class="title">{{ group.title }}</div>
|
||||
<div ref="rootEl" class="rrevdjwu" :class="{ grid }">
|
||||
<MkInput
|
||||
v-model="search"
|
||||
:placeholder="i18n.ts.search"
|
||||
type="search"
|
||||
style="margin-bottom: 16px;"
|
||||
@keydown="searchOnKeyDown"
|
||||
>
|
||||
<template #prefix><i class="ti ti-search"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<div class="items">
|
||||
<template v-for="(item, i) in group.items">
|
||||
<a v-if="item.type === 'a'" :href="item.href" :target="item.target" class="_button item" :class="{ danger: item.danger, active: item.active }">
|
||||
<span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
|
||||
<span class="text">{{ item.text }}</span>
|
||||
</a>
|
||||
<button v-else-if="item.type === 'button'" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="ev => item.action(ev)">
|
||||
<span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
|
||||
<span class="text">{{ item.text }}</span>
|
||||
</button>
|
||||
<MkA v-else :to="item.to" class="_button item" :class="{ danger: item.danger, active: item.active }">
|
||||
<span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
|
||||
<span class="text">{{ item.text }}</span>
|
||||
</MkA>
|
||||
</template>
|
||||
<template v-if="search == ''">
|
||||
<div v-for="group in def" class="group">
|
||||
<div v-if="group.title" class="title">{{ group.title }}</div>
|
||||
|
||||
<div class="items">
|
||||
<template v-for="(item, i) in group.items">
|
||||
<a v-if="item.type === 'a'" :href="item.href" :target="item.target" class="_button item" :class="{ danger: item.danger, active: item.active }">
|
||||
<span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
|
||||
<span class="text">{{ item.text }}</span>
|
||||
</a>
|
||||
<button v-else-if="item.type === 'button'" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="ev => item.action(ev)">
|
||||
<span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
|
||||
<span class="text">{{ item.text }}</span>
|
||||
</button>
|
||||
<MkA v-else :to="item.to" class="_button item" :class="{ danger: item.danger, active: item.active }">
|
||||
<span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
|
||||
<span class="text">{{ item.text }}</span>
|
||||
</MkA>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="item, index in searchResult">
|
||||
<MkA
|
||||
:to="item.path + '#' + item.id"
|
||||
class="_button searchResultItem"
|
||||
:class="{ selected: searchSelectedIndex !== null && searchSelectedIndex === index }"
|
||||
>
|
||||
<span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
|
||||
<span class="text">
|
||||
<template v-if="item.isRoot">
|
||||
{{ item.label }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<span style="opacity: 0.7; font-size: 90%;">{{ item.parentLabels.join(' > ') }}</span>
|
||||
<br>
|
||||
<span>{{ item.label }}</span>
|
||||
</template>
|
||||
</span>
|
||||
</MkA>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -58,10 +91,98 @@ export type SuperMenuDef = {
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
import { useTemplateRef, ref, watch, nextTick } from 'vue';
|
||||
import type { SearchIndexItem } from '@/scripts/autogen/settings-search-index.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { getScrollContainer } from '@@/js/scroll.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
|
||||
const props = defineProps<{
|
||||
def: SuperMenuDef[];
|
||||
grid?: boolean;
|
||||
searchIndex: SearchIndexItem[];
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
|
||||
const search = ref('');
|
||||
const searchSelectedIndex = ref<null | number>(null);
|
||||
const searchResult = ref<{
|
||||
id: string;
|
||||
path: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
isRoot: boolean;
|
||||
parentLabels: string[];
|
||||
}[]>([]);
|
||||
|
||||
watch(search, (value) => {
|
||||
searchResult.value = [];
|
||||
searchSelectedIndex.value = null;
|
||||
|
||||
if (value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const dive = (items: SearchIndexItem[], parents: SearchIndexItem[] = []) => {
|
||||
for (const item of items) {
|
||||
const matched =
|
||||
item.label.includes(value.toLowerCase()) ||
|
||||
item.keywords.some((x) => x.toLowerCase().includes(value.toLowerCase()));
|
||||
|
||||
if (matched) {
|
||||
searchResult.value.push({
|
||||
id: item.id,
|
||||
path: item.path ?? parents.find((x) => x.path != null)?.path,
|
||||
label: item.label,
|
||||
parentLabels: parents.map((x) => x.label).toReversed(),
|
||||
icon: item.icon ?? parents.find((x) => x.icon != null)?.icon,
|
||||
isRoot: parents.length === 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
dive(item.children, [item, ...parents]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
dive(props.searchIndex);
|
||||
});
|
||||
|
||||
function searchOnKeyDown(ev: KeyboardEvent) {
|
||||
if (ev.isComposing) return;
|
||||
|
||||
if (ev.key === 'Enter' && searchSelectedIndex.value != null) {
|
||||
ev.preventDefault();
|
||||
router.push(searchResult.value[searchSelectedIndex.value].path + '#' + searchResult.value[searchSelectedIndex.value].id);
|
||||
} else if (ev.key === 'ArrowDown') {
|
||||
ev.preventDefault();
|
||||
const current = searchSelectedIndex.value ?? -1;
|
||||
searchSelectedIndex.value = current + 1 >= searchResult.value.length ? 0 : current + 1;
|
||||
} else if (ev.key === 'ArrowUp') {
|
||||
ev.preventDefault();
|
||||
const current = searchSelectedIndex.value ?? 0;
|
||||
searchSelectedIndex.value = current - 1 < 0 ? searchResult.value.length - 1 : current - 1;
|
||||
}
|
||||
|
||||
if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') {
|
||||
nextTick(() => {
|
||||
if (!rootEl.value) return;
|
||||
const selectedEl = rootEl.value.querySelector<HTMLElement>('.searchResultItem.selected');
|
||||
if (selectedEl != null) {
|
||||
const scrollContainer = getScrollContainer(selectedEl);
|
||||
if (!scrollContainer) return;
|
||||
scrollContainer.scrollTo({
|
||||
top: selectedEl.offsetTop - scrollContainer.clientHeight / 2 + selectedEl.clientHeight / 2,
|
||||
behavior: 'instant',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -184,5 +305,52 @@ defineProps<{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.searchResultItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 9px 16px 9px 8px;
|
||||
border-radius: 9px;
|
||||
font-size: 0.9em;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background: var(--MI_THEME-panelHighlight);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
outline: 2px solid var(--MI_THEME-focus);
|
||||
}
|
||||
|
||||
&:focus-visible,
|
||||
&.selected {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--MI_THEME-accent);
|
||||
background: var(--MI_THEME-accentedBg);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: var(--MI_THEME-error);
|
||||
}
|
||||
|
||||
> .icon {
|
||||
width: 32px;
|
||||
margin-right: 2px;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
> .text {
|
||||
white-space: normal;
|
||||
padding-right: 12px;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
14
packages/frontend/src/components/global/SearchKeyword.vue
Normal file
14
packages/frontend/src/components/global/SearchKeyword.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
</style>
|
14
packages/frontend/src/components/global/SearchLabel.vue
Normal file
14
packages/frontend/src/components/global/SearchLabel.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
</style>
|
116
packages/frontend/src/components/global/SearchMarker.vue
Normal file
116
packages/frontend/src/components/global/SearchMarker.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div ref="root" :class="[$style.root, { [$style.highlighted]: highlighted }]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
onActivated,
|
||||
onDeactivated,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
watch,
|
||||
computed,
|
||||
ref,
|
||||
useTemplateRef,
|
||||
inject,
|
||||
} from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
markerId?: string;
|
||||
label?: string;
|
||||
icon?: string;
|
||||
keywords?: string[];
|
||||
children?: string[];
|
||||
inlining?: string[];
|
||||
}>();
|
||||
|
||||
const rootEl = useTemplateRef('root');
|
||||
const rootElMutationObserver = new MutationObserver(() => {
|
||||
checkChildren();
|
||||
});
|
||||
const injectedSearchMarkerId = inject<Ref<string | null>>('inAppSearchMarkerId');
|
||||
const searchMarkerId = computed(() => injectedSearchMarkerId?.value ?? window.location.hash.slice(1));
|
||||
const highlighted = ref(props.markerId === searchMarkerId.value);
|
||||
|
||||
function checkChildren() {
|
||||
if (props.children?.includes(searchMarkerId.value)) {
|
||||
const el = document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`);
|
||||
highlighted.value = el == null;
|
||||
}
|
||||
}
|
||||
|
||||
watch([
|
||||
searchMarkerId,
|
||||
() => props.children,
|
||||
], () => {
|
||||
if (props.children != null && props.children.length > 0) {
|
||||
checkChildren();
|
||||
}
|
||||
}, { flush: 'post' });
|
||||
|
||||
function init() {
|
||||
checkChildren();
|
||||
|
||||
if (highlighted.value) {
|
||||
rootEl.value?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
});
|
||||
}
|
||||
|
||||
if (rootEl.value != null) {
|
||||
rootElMutationObserver.observe(rootEl.value, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
rootElMutationObserver.disconnect();
|
||||
}
|
||||
|
||||
onMounted(init);
|
||||
onActivated(init);
|
||||
onDeactivated(dispose);
|
||||
onBeforeUnmount(dispose);
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -8px;
|
||||
width: calc(100% + 16px);
|
||||
height: calc(100% + 16px);
|
||||
border-radius: 6px;
|
||||
animation: blink 1s 3.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% {
|
||||
background: color(from var(--MI_THEME-accent) srgb r g b / 0.05);
|
||||
border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.7);
|
||||
}
|
||||
50% {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -3,8 +3,6 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { App } from 'vue';
|
||||
|
||||
import Mfm from './global/MkMfm.js';
|
||||
import MkA from './global/MkA.vue';
|
||||
import MkAcct from './global/MkAcct.vue';
|
||||
@ -26,6 +24,11 @@ import MkSpacer from './global/MkSpacer.vue';
|
||||
import MkFooterSpacer from './global/MkFooterSpacer.vue';
|
||||
import MkStickyContainer from './global/MkStickyContainer.vue';
|
||||
import MkLazy from './global/MkLazy.vue';
|
||||
import SearchMarker from './global/SearchMarker.vue';
|
||||
import SearchLabel from './global/SearchLabel.vue';
|
||||
import SearchKeyword from './global/SearchKeyword.vue';
|
||||
|
||||
import type { App } from 'vue';
|
||||
|
||||
export default function(app: App) {
|
||||
for (const [key, value] of Object.entries(components)) {
|
||||
@ -55,6 +58,9 @@ export const components = {
|
||||
MkFooterSpacer: MkFooterSpacer,
|
||||
MkStickyContainer: MkStickyContainer,
|
||||
MkLazy: MkLazy,
|
||||
SearchMarker: SearchMarker,
|
||||
SearchLabel: SearchLabel,
|
||||
SearchKeyword: SearchKeyword,
|
||||
};
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
@ -80,5 +86,8 @@ declare module '@vue/runtime-core' {
|
||||
MkFooterSpacer: typeof MkFooterSpacer;
|
||||
MkStickyContainer: typeof MkStickyContainer;
|
||||
MkLazy: typeof MkLazy;
|
||||
SearchMarker: typeof SearchMarker;
|
||||
SearchLabel: typeof SearchLabel;
|
||||
SearchKeyword: typeof SearchKeyword;
|
||||
}
|
||||
}
|
||||
|
@ -4,74 +4,82 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<FormSection :first="first">
|
||||
<template #label>{{ i18n.ts['2fa'] }}</template>
|
||||
<SearchMarker markerId="2fa" :keywords="['2fa']">
|
||||
<FormSection :first="first">
|
||||
<template #label><SearchLabel>{{ i18n.ts['2fa'] }}</SearchLabel></template>
|
||||
|
||||
<div v-if="$i" class="_gaps_s">
|
||||
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'partial'" warn>
|
||||
{{ i18n.ts._2fa.backupCodeUsedWarning }}
|
||||
</MkInfo>
|
||||
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'none'" warn>
|
||||
{{ i18n.ts._2fa.backupCodesExhaustedWarning }}
|
||||
</MkInfo>
|
||||
<div v-if="$i" class="_gaps_s">
|
||||
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'partial'" warn>
|
||||
{{ i18n.ts._2fa.backupCodeUsedWarning }}
|
||||
</MkInfo>
|
||||
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'none'" warn>
|
||||
{{ i18n.ts._2fa.backupCodesExhaustedWarning }}
|
||||
</MkInfo>
|
||||
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-shield-lock"></i></template>
|
||||
<template #label>{{ i18n.ts.totp }}</template>
|
||||
<template #caption>{{ i18n.ts.totpDescription }}</template>
|
||||
<template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
|
||||
<SearchMarker :keywords="['totp', 'app']">
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-shield-lock"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.totp }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.totpDescription }}</SearchKeyword></template>
|
||||
<template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
|
||||
|
||||
<div v-if="$i.twoFactorEnabled" class="_gaps_s">
|
||||
<div v-text="i18n.ts._2fa.alreadyRegistered"/>
|
||||
<template v-if="$i.securityKeysList.length > 0">
|
||||
<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
|
||||
<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
|
||||
</template>
|
||||
<MkButton v-else danger @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
|
||||
</div>
|
||||
<div v-if="$i.twoFactorEnabled" class="_gaps_s">
|
||||
<div v-text="i18n.ts._2fa.alreadyRegistered"/>
|
||||
<template v-if="$i.securityKeysList.length > 0">
|
||||
<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
|
||||
<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
|
||||
</template>
|
||||
<MkButton v-else danger @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!$i.twoFactorEnabled" class="_gaps_s">
|
||||
<MkButton primary gradate @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
|
||||
<MkLink url="https://misskey-hub.net/docs/for-users/stepped-guides/how-to-enable-2fa/" target="_blank"><i class="ti ti-help-circle"></i> {{ i18n.ts.learnMore }}</MkLink>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<div v-else-if="!$i.twoFactorEnabled" class="_gaps_s">
|
||||
<MkButton primary gradate @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
|
||||
<MkLink url="https://misskey-hub.net/docs/for-users/stepped-guides/how-to-enable-2fa/" target="_blank"><i class="ti ti-help-circle"></i> {{ i18n.ts.learnMore }}</MkLink>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.securityKeyAndPasskey }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkInfo>
|
||||
{{ i18n.ts._2fa.securityKeyInfo }}
|
||||
</MkInfo>
|
||||
<SearchMarker :keywords="['security', 'key', 'passkey']">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-key"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.securityKeyAndPasskey }}</SearchLabel></template>
|
||||
<div class="_gaps_s">
|
||||
<MkInfo>
|
||||
{{ i18n.ts._2fa.securityKeyInfo }}
|
||||
</MkInfo>
|
||||
|
||||
<MkInfo v-if="!webAuthnSupported()" warn>
|
||||
{{ i18n.ts._2fa.securityKeyNotSupported }}
|
||||
</MkInfo>
|
||||
<MkInfo v-if="!webAuthnSupported()" warn>
|
||||
{{ i18n.ts._2fa.securityKeyNotSupported }}
|
||||
</MkInfo>
|
||||
|
||||
<MkInfo v-else-if="webAuthnSupported() && !$i.twoFactorEnabled" warn>
|
||||
{{ i18n.ts._2fa.registerTOTPBeforeKey }}
|
||||
</MkInfo>
|
||||
<MkInfo v-else-if="webAuthnSupported() && !$i.twoFactorEnabled" warn>
|
||||
{{ i18n.ts._2fa.registerTOTPBeforeKey }}
|
||||
</MkInfo>
|
||||
|
||||
<template v-else>
|
||||
<MkButton primary @click="addSecurityKey">{{ i18n.ts._2fa.registerSecurityKey }}</MkButton>
|
||||
<MkFolder v-for="key in $i.securityKeysList" :key="key.id">
|
||||
<template #label>{{ key.name }}</template>
|
||||
<template #suffix><I18n :src="i18n.ts.lastUsedAt"><template #t><MkTime :time="key.lastUsed"/></template></I18n></template>
|
||||
<div class="_buttons">
|
||||
<MkButton @click="renameKey(key)"><i class="ti ti-forms"></i> {{ i18n.ts.rename }}</MkButton>
|
||||
<MkButton danger @click="unregisterKey(key)"><i class="ti ti-trash"></i> {{ i18n.ts.unregister }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<template v-else>
|
||||
<MkButton primary @click="addSecurityKey">{{ i18n.ts._2fa.registerSecurityKey }}</MkButton>
|
||||
<MkFolder v-for="key in $i.securityKeysList" :key="key.id">
|
||||
<template #label>{{ key.name }}</template>
|
||||
<template #suffix><I18n :src="i18n.ts.lastUsedAt"><template #t><MkTime :time="key.lastUsed"/></template></I18n></template>
|
||||
<div class="_buttons">
|
||||
<MkButton @click="renameKey(key)"><i class="ti ti-forms"></i> {{ i18n.ts.rename }}</MkButton>
|
||||
<MkButton danger @click="unregisterKey(key)"><i class="ti ti-trash"></i> {{ i18n.ts.unregister }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
|
||||
<template #label>{{ i18n.ts.passwordLessLogin }}</template>
|
||||
<template #caption>{{ i18n.ts.passwordLessLoginDescription }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
<SearchMarker :keywords="['password', 'less', 'key', 'passkey', 'login', 'signin']">
|
||||
<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
|
||||
<template #label><SearchLabel>{{ i18n.ts.passwordLessLogin }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.passwordLessLoginDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
91
packages/frontend/src/pages/settings/accessibility.vue
Normal file
91
packages/frontend/src/pages/settings/accessibility.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<SearchMarker path="/settings/accessibility" :label="i18n.ts.accessibility" :keywords="['accessibility']" icon="ti ti-accessible">
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['animation', 'motion', 'reduce']">
|
||||
<MkSwitch v-model="reduceAnimation">
|
||||
<template #label><SearchLabel>{{ i18n.ts.reduceUiAnimation }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif']">
|
||||
<MkSwitch v-model="disableShowingAnimatedImages">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableShowingAnimatedImages }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['mfm', 'enable', 'show', 'animated']">
|
||||
<MkSwitch v-model="animatedMfm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAnimatedMfm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['swipe', 'horizontal', 'tab']">
|
||||
<MkSwitch v-model="enableHorizontalSwipe">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableHorizontalSwipe }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['keep', 'screen', 'display', 'on']">
|
||||
<MkSwitch v-model="keepScreenOn">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepScreenOn }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['native', 'system', 'video', 'audio', 'player', 'media']">
|
||||
<MkSwitch v-model="useNativeUIForVideoAudioPlayer">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
<SearchMarker :keywords="['contextmenu', 'system', 'native']">
|
||||
<MkSelect v-model="contextMenu">
|
||||
<template #label><SearchLabel>{{ i18n.ts._contextMenu.title }}</SearchLabel></template>
|
||||
<option value="app">{{ i18n.ts._contextMenu.app }}</option>
|
||||
<option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option>
|
||||
<option value="native">{{ i18n.ts._contextMenu.native }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
|
||||
const animatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm'));
|
||||
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
|
||||
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
||||
const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
|
||||
const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer'));
|
||||
const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
|
||||
|
||||
watch([
|
||||
keepScreenOn,
|
||||
contextMenu,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: i18n.ts.accessibility,
|
||||
icon: 'ti ti-accessible',
|
||||
}));
|
||||
</script>
|
287
packages/frontend/src/pages/settings/appearance.vue
Normal file
287
packages/frontend/src/pages/settings/appearance.vue
Normal file
@ -0,0 +1,287 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<SearchMarker path="/settings/appearance" :label="i18n.ts.appearance" :keywords="['appearance']" icon="ti ti-device-desktop">
|
||||
<div class="_gaps_m">
|
||||
<FormSection first>
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['blur']">
|
||||
<MkSwitch v-model="useBlurEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['blur', 'modal']">
|
||||
<MkSwitch v-model="useBlurEffectForModal">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail']">
|
||||
<MkSwitch v-model="highlightSensitiveMedia">
|
||||
<template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['avatar', 'icon', 'square']">
|
||||
<MkSwitch v-model="squareAvatars">
|
||||
<template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']">
|
||||
<MkSwitch v-model="showAvatarDecorations">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['note', 'timeline', 'gap']">
|
||||
<MkSwitch v-model="showGapBetweenNotesInTimeline">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['font', 'system', 'native']">
|
||||
<MkSwitch v-model="useSystemFont">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['effect', 'show']">
|
||||
<MkSwitch v-model="enableSeasonalScreenEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']">
|
||||
<MkSelect v-model="menuStyle">
|
||||
<template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template>
|
||||
<option value="auto">{{ i18n.ts.auto }}</option>
|
||||
<option value="popup">{{ i18n.ts.popup }}</option>
|
||||
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']">
|
||||
<div>
|
||||
<MkRadios v-model="emojiStyle">
|
||||
<template #label><SearchLabel>{{ i18n.ts.emojiStyle }}</SearchLabel></template>
|
||||
<option value="native">{{ i18n.ts.native }}</option>
|
||||
<option value="fluentEmoji">Fluent Emoji</option>
|
||||
<option value="twemoji">Twemoji</option>
|
||||
</MkRadios>
|
||||
<div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['font', 'size']">
|
||||
<MkRadios v-model="fontSize">
|
||||
<template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template>
|
||||
<option :value="null"><span style="font-size: 14px;">Aa</span></option>
|
||||
<option value="1"><span style="font-size: 15px;">Aa</span></option>
|
||||
<option value="2"><span style="font-size: 16px;">Aa</span></option>
|
||||
<option value="3"><span style="font-size: 17px;">Aa</span></option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<SearchMarker :keywords="['note', 'display']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.displayOfNote }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['reaction', 'size', 'scale', 'display']">
|
||||
<MkRadios v-model="reactionsDisplaySize">
|
||||
<template #label><SearchLabel>{{ i18n.ts.reactionsDisplaySize }}</SearchLabel></template>
|
||||
<option value="small">{{ i18n.ts.small }}</option>
|
||||
<option value="medium">{{ i18n.ts.medium }}</option>
|
||||
<option value="large">{{ i18n.ts.large }}</option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['reaction', 'size', 'scale', 'display', 'width', 'limit']">
|
||||
<MkSwitch v-model="limitWidthOfReaction">
|
||||
<template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height']">
|
||||
<MkRadios v-model="mediaListWithOneImageAppearance">
|
||||
<template #label><SearchLabel>{{ i18n.ts.mediaListWithOneImageAppearance }}</SearchLabel></template>
|
||||
<option value="expand">{{ i18n.ts.default }}</option>
|
||||
<option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option>
|
||||
<option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option>
|
||||
<option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation']">
|
||||
<MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker">
|
||||
<template #label><SearchLabel>{{ i18n.ts.instanceTicker }}</SearchLabel></template>
|
||||
<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
|
||||
<option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
|
||||
<option value="always">{{ i18n.ts._instanceTicker.always }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility']">
|
||||
<MkSelect v-model="nsfw">
|
||||
<template #label><SearchLabel>{{ i18n.ts.displayOfSensitiveMedia }}</SearchLabel></template>
|
||||
<option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option>
|
||||
<option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option>
|
||||
<option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['notification', 'display']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.notificationDisplay }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['position']">
|
||||
<MkRadios v-model="notificationPosition">
|
||||
<template #label><SearchLabel>{{ i18n.ts.position }}</SearchLabel></template>
|
||||
<option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option>
|
||||
<option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option>
|
||||
<option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option>
|
||||
<option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['stack', 'axis', 'direction']">
|
||||
<MkRadios v-model="notificationStackAxis">
|
||||
<template #label><SearchLabel>{{ i18n.ts.stackAxis }}</SearchLabel></template>
|
||||
<option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option>
|
||||
<option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
|
||||
</FormSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import { globalEvents } from '@/events.js';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import { instance } from '@/instance.js';
|
||||
|
||||
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||
|
||||
const showAvatarDecorations = computed(defaultStore.makeGetterSetter('showAvatarDecorations'));
|
||||
const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
|
||||
const menuStyle = computed(defaultStore.makeGetterSetter('menuStyle'));
|
||||
const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
|
||||
const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));
|
||||
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
|
||||
const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
|
||||
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
|
||||
const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'));
|
||||
const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
|
||||
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
|
||||
const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
|
||||
const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
|
||||
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
|
||||
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
|
||||
const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'));
|
||||
|
||||
watch(fontSize, () => {
|
||||
if (fontSize.value == null) {
|
||||
miLocalStorage.removeItem('fontSize');
|
||||
} else {
|
||||
miLocalStorage.setItem('fontSize', fontSize.value);
|
||||
}
|
||||
});
|
||||
|
||||
watch(useSystemFont, () => {
|
||||
if (useSystemFont.value) {
|
||||
miLocalStorage.setItem('useSystemFont', 't');
|
||||
} else {
|
||||
miLocalStorage.removeItem('useSystemFont');
|
||||
}
|
||||
});
|
||||
|
||||
watch([
|
||||
fontSize,
|
||||
useSystemFont,
|
||||
squareAvatars,
|
||||
highlightSensitiveMedia,
|
||||
enableSeasonalScreenEffect,
|
||||
showGapBetweenNotesInTimeline,
|
||||
mediaListWithOneImageAppearance,
|
||||
reactionsDisplaySize,
|
||||
limitWidthOfReaction,
|
||||
mediaListWithOneImageAppearance,
|
||||
reactionsDisplaySize,
|
||||
limitWidthOfReaction,
|
||||
instanceTicker,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
let smashCount = 0;
|
||||
let smashTimer: number | null = null;
|
||||
|
||||
function testNotification(): void {
|
||||
const notification: Misskey.entities.Notification = {
|
||||
id: Math.random().toString(),
|
||||
createdAt: new Date().toUTCString(),
|
||||
isRead: false,
|
||||
type: 'test',
|
||||
};
|
||||
|
||||
globalEvents.emit('clientNotification', notification);
|
||||
|
||||
// セルフ通知破壊 実績関連
|
||||
smashCount++;
|
||||
if (smashCount >= 10) {
|
||||
claimAchievement('smashTestNotificationButton');
|
||||
smashCount = 0;
|
||||
}
|
||||
if (smashTimer) {
|
||||
clearTimeout(smashTimer);
|
||||
}
|
||||
smashTimer = window.setTimeout(() => {
|
||||
smashCount = 0;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: i18n.ts.appearance,
|
||||
icon: 'ti ti-device-desktop',
|
||||
}));
|
||||
</script>
|
@ -4,44 +4,46 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!loading" class="_gaps">
|
||||
<MkInfo>{{ i18n.tsx._profile.avatarDecorationMax({ max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.tsx.remainingN({ n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
||||
<SearchMarker path="/settings/avatar-decoration" :label="i18n.ts.avatarDecorations" :keywords="['avatar', 'icon', 'decoration']" icon="ti ti-sparkles">
|
||||
<div>
|
||||
<div v-if="!loading" class="_gaps">
|
||||
<MkInfo>{{ i18n.tsx._profile.avatarDecorationMax({ max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.tsx.remainingN({ n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
||||
|
||||
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
|
||||
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
|
||||
|
||||
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
|
||||
<div>{{ i18n.ts.inUse }}</div>
|
||||
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
|
||||
<div>{{ i18n.ts.inUse }}</div>
|
||||
|
||||
<div :class="$style.decorations">
|
||||
<XDecoration
|
||||
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
||||
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
||||
:angle="avatarDecoration.angle"
|
||||
:flipH="avatarDecoration.flipH"
|
||||
:offsetX="avatarDecoration.offsetX"
|
||||
:offsetY="avatarDecoration.offsetY"
|
||||
:active="true"
|
||||
@click="openDecoration(avatarDecoration, i)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
|
||||
</div>
|
||||
|
||||
<div :class="$style.decorations">
|
||||
<XDecoration
|
||||
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
||||
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
||||
:angle="avatarDecoration.angle"
|
||||
:flipH="avatarDecoration.flipH"
|
||||
:offsetX="avatarDecoration.offsetX"
|
||||
:offsetY="avatarDecoration.offsetY"
|
||||
:active="true"
|
||||
@click="openDecoration(avatarDecoration, i)"
|
||||
v-for="avatarDecoration in avatarDecorations"
|
||||
:key="avatarDecoration.id"
|
||||
:decoration="avatarDecoration"
|
||||
@click="openDecoration(avatarDecoration)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
|
||||
</div>
|
||||
|
||||
<div :class="$style.decorations">
|
||||
<XDecoration
|
||||
v-for="avatarDecoration in avatarDecorations"
|
||||
:key="avatarDecoration.id"
|
||||
:decoration="avatarDecoration"
|
||||
@click="openDecoration(avatarDecoration)"
|
||||
/>
|
||||
<div v-else>
|
||||
<MkLoading/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<MkLoading/>
|
||||
</div>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -4,60 +4,81 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<FormSection v-if="!fetching" first>
|
||||
<template #label>{{ i18n.ts.usageAmount }}</template>
|
||||
<SearchMarker path="/settings/drive" :label="i18n.ts.drive" :keywords="['drive']" icon="ti ti-cloud">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['capacity', 'usage']">
|
||||
<FormSection first>
|
||||
<template #label><SearchLabel>{{ i18n.ts.usageAmount }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div>
|
||||
<div :class="$style.meter"><div :class="$style.meterValue" :style="meterStyle"></div></div>
|
||||
<div v-if="!fetching" class="_gaps_m">
|
||||
<div>
|
||||
<div :class="$style.meter"><div :class="$style.meterValue" :style="meterStyle"></div></div>
|
||||
</div>
|
||||
<FormSplit>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.capacity }}</template>
|
||||
<template #value>{{ bytes(capacity, 1) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.inUse }}</template>
|
||||
<template #value>{{ bytes(usage, 1) }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSplit>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['statistics', 'usage']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.statistics }}</SearchLabel></template>
|
||||
<MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspectRatio="6"/>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['default', 'upload', 'folder']">
|
||||
<FormLink @click="chooseUploadFolder()">
|
||||
<SearchLabel>{{ i18n.ts.uploadFolder }}</SearchLabel>
|
||||
<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
|
||||
<template #suffixIcon><i class="ti ti-folder"></i></template>
|
||||
</FormLink>
|
||||
</SearchMarker>
|
||||
|
||||
<FormLink to="/settings/drive/cleaner">
|
||||
{{ i18n.ts.drivecleaner }}
|
||||
</FormLink>
|
||||
|
||||
<SearchMarker :keywords="['keep', 'original', 'raw', 'upload']">
|
||||
<MkSwitch v-model="keepOriginalUploading">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepOriginalUploading }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.keepOriginalUploadingDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['keep', 'original', 'filename']">
|
||||
<MkSwitch v-model="keepOriginalFilename">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepOriginalFilename }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file']">
|
||||
<MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.alwaysMarkSensitive }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['auto', 'nsfw', 'sensitive', 'media', 'file']">
|
||||
<MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAutoSensitive }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.enableAutoSensitiveDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
<FormSplit>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.capacity }}</template>
|
||||
<template #value>{{ bytes(capacity, 1) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.inUse }}</template>
|
||||
<template #value>{{ bytes(usage, 1) }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSplit>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.statistics }}</template>
|
||||
<MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspectRatio="6"/>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_m">
|
||||
<FormLink @click="chooseUploadFolder()">
|
||||
{{ i18n.ts.uploadFolder }}
|
||||
<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
|
||||
<template #suffixIcon><i class="ti ti-folder"></i></template>
|
||||
</FormLink>
|
||||
<FormLink to="/settings/drive/cleaner">
|
||||
{{ i18n.ts.drivecleaner }}
|
||||
</FormLink>
|
||||
<MkSwitch v-model="keepOriginalUploading">
|
||||
<template #label>{{ i18n.ts.keepOriginalUploading }}</template>
|
||||
<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="keepOriginalFilename">
|
||||
<template #label>{{ i18n.ts.keepOriginalFilename }}</template>
|
||||
<template #caption>{{ i18n.ts.keepOriginalFilenameDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()">
|
||||
<template #label>{{ i18n.ts.alwaysMarkSensitive }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()">
|
||||
<template #label>{{ i18n.ts.enableAutoSensitive }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption>{{ i18n.ts.enableAutoSensitiveDescription }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -4,47 +4,58 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="instance.enableEmail" class="_gaps_m">
|
||||
<FormSection first>
|
||||
<template #label>{{ i18n.ts.emailAddress }}</template>
|
||||
<MkInput v-model="emailAddress" type="email" manualSave>
|
||||
<template #prefix><i class="ti ti-mail"></i></template>
|
||||
<template v-if="$i.email && !$i.emailVerified" #caption>{{ i18n.ts.verificationEmailSent }}</template>
|
||||
<template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i> {{ i18n.ts.emailVerified }}</template>
|
||||
</MkInput>
|
||||
</FormSection>
|
||||
<SearchMarker path="/settings/email" :label="i18n.ts.email" :keywords="['email']" icon="ti ti-mail">
|
||||
<div class="_gaps_m">
|
||||
<MkInfo v-if="!instance.enableEmail">{{ i18n.ts.emailNotSupported }}</MkInfo>
|
||||
|
||||
<FormSection>
|
||||
<MkSwitch :modelValue="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail">
|
||||
{{ i18n.ts.receiveAnnouncementFromInstance }}
|
||||
</MkSwitch>
|
||||
</FormSection>
|
||||
<MkDisableSection :disabled="!instance.enableEmail">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['email', 'address']">
|
||||
<FormSection first>
|
||||
<template #label><SearchLabel>{{ i18n.ts.emailAddress }}</SearchLabel></template>
|
||||
<MkInput v-model="emailAddress" type="email" manualSave>
|
||||
<template #prefix><i class="ti ti-mail"></i></template>
|
||||
<template v-if="$i.email && !$i.emailVerified" #caption>{{ i18n.ts.verificationEmailSent }}</template>
|
||||
<template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i> {{ i18n.ts.emailVerified }}</template>
|
||||
</MkInput>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.emailNotification }}</template>
|
||||
<FormSection>
|
||||
<SearchMarker :keywords="['announcement', 'email']">
|
||||
<MkSwitch :modelValue="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail">
|
||||
<template #label><SearchLabel>{{ i18n.ts.receiveAnnouncementFromInstance }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</FormSection>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="emailNotification_mention">
|
||||
{{ i18n.ts._notification._types.mention }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_reply">
|
||||
{{ i18n.ts._notification._types.reply }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_quote">
|
||||
{{ i18n.ts._notification._types.quote }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_follow">
|
||||
{{ i18n.ts._notification._types.follow }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_receiveFollowRequest">
|
||||
{{ i18n.ts._notification._types.receiveFollowRequest }}
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
<div v-if="!instance.enableEmail" class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.emailNotSupported }}</MkInfo>
|
||||
</div>
|
||||
<SearchMarker :keywords="['notification', 'email']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.emailNotification }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="emailNotification_mention">
|
||||
{{ i18n.ts._notification._types.mention }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_reply">
|
||||
{{ i18n.ts._notification._types.reply }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_quote">
|
||||
{{ i18n.ts._notification._types.quote }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_follow">
|
||||
{{ i18n.ts._notification._types.follow }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_receiveFollowRequest">
|
||||
{{ i18n.ts._notification._types.receiveFollowRequest }}
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkDisableSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -53,6 +64,7 @@ import FormSection from '@/components/form/section.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkDisableSection from '@/components/MkDisableSection.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { signinRequired } from '@/account.js';
|
||||
|
@ -1,492 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<MkSelect v-model="lang">
|
||||
<template #label>{{ i18n.ts.uiLanguage }}</template>
|
||||
<option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option>
|
||||
<template #caption>
|
||||
<I18n :src="i18n.ts.i18nInfo" tag="span">
|
||||
<template #link>
|
||||
<MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
|
||||
</template>
|
||||
</I18n>
|
||||
</template>
|
||||
</MkSelect>
|
||||
|
||||
<MkRadios v-model="overridedDeviceKind">
|
||||
<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
|
||||
<option :value="null">{{ i18n.ts.auto }}</option>
|
||||
<option value="smartphone"><i class="ti ti-device-mobile"/> {{ i18n.ts.smartphone }}</option>
|
||||
<option value="tablet"><i class="ti ti-device-tablet"/> {{ i18n.ts.tablet }}</option>
|
||||
<option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option>
|
||||
</MkRadios>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
|
||||
<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.pinnedList }}</template>
|
||||
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
|
||||
<MkButton v-if="defaultStore.reactiveState.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton>
|
||||
<MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.displayOfNote }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="collapseRenotes">
|
||||
<template #label>{{ i18n.ts.collapseRenotes }}</template>
|
||||
<template #caption>{{ i18n.ts.collapseRenotesDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch>
|
||||
<MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch>
|
||||
<MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
|
||||
<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
|
||||
<MkSwitch v-if="advancedMfm" v-model="enableQuickAddMfmFunction">{{ i18n.ts.enableQuickAddMfmFunction }}</MkSwitch>
|
||||
<MkSwitch v-model="showReactionsCount">{{ i18n.ts.showReactionsCount }}</MkSwitch>
|
||||
<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
|
||||
<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
|
||||
<MkRadios v-model="reactionsDisplaySize">
|
||||
<template #label>{{ i18n.ts.reactionsDisplaySize }}</template>
|
||||
<option value="small">{{ i18n.ts.small }}</option>
|
||||
<option value="medium">{{ i18n.ts.medium }}</option>
|
||||
<option value="large">{{ i18n.ts.large }}</option>
|
||||
</MkRadios>
|
||||
<MkSwitch v-model="limitWidthOfReaction">{{ i18n.ts.limitWidthOfReaction }}</MkSwitch>
|
||||
</div>
|
||||
|
||||
<MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker">
|
||||
<template #label>{{ i18n.ts.instanceTicker }}</template>
|
||||
<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
|
||||
<option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
|
||||
<option value="always">{{ i18n.ts._instanceTicker.always }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSelect v-model="nsfw">
|
||||
<template #label>{{ i18n.ts.displayOfSensitiveMedia }}</template>
|
||||
<option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option>
|
||||
<option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option>
|
||||
<option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkRadios v-model="mediaListWithOneImageAppearance">
|
||||
<template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
|
||||
<option value="expand">{{ i18n.ts.default }}</option>
|
||||
<option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option>
|
||||
<option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option>
|
||||
<option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option>
|
||||
</MkRadios>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.notificationDisplay }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="useGroupedNotifications">{{ i18n.ts.useGroupedNotifications }}</MkSwitch>
|
||||
|
||||
<MkRadios v-model="notificationPosition">
|
||||
<template #label>{{ i18n.ts.position }}</template>
|
||||
<option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option>
|
||||
<option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option>
|
||||
<option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option>
|
||||
<option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option>
|
||||
</MkRadios>
|
||||
|
||||
<MkRadios v-model="notificationStackAxis">
|
||||
<template #label>{{ i18n.ts.stackAxis }}</template>
|
||||
<option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option>
|
||||
<option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
|
||||
</MkRadios>
|
||||
|
||||
<MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.appearance }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="reduceAnimation">{{ i18n.ts.reduceUiAnimation }}</MkSwitch>
|
||||
<MkSwitch v-model="useBlurEffect">{{ i18n.ts.useBlurEffect }}</MkSwitch>
|
||||
<MkSwitch v-model="useBlurEffectForModal">{{ i18n.ts.useBlurEffectForModal }}</MkSwitch>
|
||||
<MkSwitch v-model="disableShowingAnimatedImages">{{ i18n.ts.disableShowingAnimatedImages }}</MkSwitch>
|
||||
<MkSwitch v-model="highlightSensitiveMedia">{{ i18n.ts.highlightSensitiveMedia }}</MkSwitch>
|
||||
<MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch>
|
||||
<MkSwitch v-model="showAvatarDecorations">{{ i18n.ts.showAvatarDecorations }}</MkSwitch>
|
||||
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
|
||||
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
|
||||
<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
|
||||
<MkSwitch v-model="useNativeUIForVideoAudioPlayer">{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</MkSwitch>
|
||||
</div>
|
||||
|
||||
<MkSelect v-model="menuStyle">
|
||||
<template #label>{{ i18n.ts.menuStyle }}</template>
|
||||
<option value="auto">{{ i18n.ts.auto }}</option>
|
||||
<option value="popup">{{ i18n.ts.popup }}</option>
|
||||
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<div>
|
||||
<MkRadios v-model="emojiStyle">
|
||||
<template #label>{{ i18n.ts.emojiStyle }}</template>
|
||||
<option value="native">{{ i18n.ts.native }}</option>
|
||||
<option value="fluentEmoji">Fluent Emoji</option>
|
||||
<option value="twemoji">Twemoji</option>
|
||||
</MkRadios>
|
||||
<div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div>
|
||||
</div>
|
||||
|
||||
<MkRadios v-model="fontSize">
|
||||
<template #label>{{ i18n.ts.fontSize }}</template>
|
||||
<option :value="null"><span style="font-size: 14px;">Aa</span></option>
|
||||
<option value="1"><span style="font-size: 15px;">Aa</span></option>
|
||||
<option value="2"><span style="font-size: 16px;">Aa</span></option>
|
||||
<option value="3"><span style="font-size: 17px;">Aa</span></option>
|
||||
</MkRadios>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.behavior }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch>
|
||||
<MkSwitch v-model="useReactionPickerForContextMenu">{{ i18n.ts.useReactionPickerForContextMenu }}</MkSwitch>
|
||||
<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
|
||||
<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
|
||||
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
|
||||
<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
|
||||
<MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
|
||||
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch>
|
||||
<MkSwitch v-model="confirmOnReact">{{ i18n.ts.confirmOnReact }}</MkSwitch>
|
||||
</div>
|
||||
<MkSelect v-model="serverDisconnectedBehavior">
|
||||
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
||||
<option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option>
|
||||
<option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
|
||||
<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
|
||||
</MkSelect>
|
||||
<MkSelect v-model="contextMenu">
|
||||
<template #label>{{ i18n.ts._contextMenu.title }}</template>
|
||||
<option value="app">{{ i18n.ts._contextMenu.app }}</option>
|
||||
<option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option>
|
||||
<option value="native">{{ i18n.ts._contextMenu.native }}</option>
|
||||
</MkSelect>
|
||||
<MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing>
|
||||
<template #label>{{ i18n.ts.numberOfPageCache }}</template>
|
||||
<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
|
||||
</MkRange>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.dataSaver }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
|
||||
<MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
|
||||
</div>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="dataSaver.media">
|
||||
{{ i18n.ts._dataSaver._media.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.avatar">
|
||||
{{ i18n.ts._dataSaver._avatar.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.urlPreview">
|
||||
{{ i18n.ts._dataSaver._urlPreview.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.code">
|
||||
{{ i18n.ts._dataSaver._code.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.other }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkRadios v-model="hemisphere">
|
||||
<template #label>{{ i18n.ts.hemisphere }}</template>
|
||||
<option value="N">{{ i18n.ts._hemisphere.N }}</option>
|
||||
<option value="S">{{ i18n.ts._hemisphere.S }}</option>
|
||||
<template #caption>{{ i18n.ts._hemisphere.caption }}</template>
|
||||
</MkRadios>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.additionalEmojiDictionary }}</template>
|
||||
<div class="_buttons">
|
||||
<template v-for="lang in emojiIndexLangs" :key="lang">
|
||||
<MkButton v-if="defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton>
|
||||
<MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
|
||||
<FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { langs } from '@@/js/config.js';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { globalEvents } from '@/events.js';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
|
||||
const lang = ref(miLocalStorage.getItem('lang'));
|
||||
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||
const dataSaver = ref(defaultStore.state.dataSaver);
|
||||
|
||||
const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
|
||||
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
|
||||
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
|
||||
const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
|
||||
const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
|
||||
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
|
||||
const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
|
||||
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
|
||||
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
|
||||
const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
|
||||
const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));
|
||||
const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'));
|
||||
const animatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm'));
|
||||
const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm'));
|
||||
const showReactionsCount = computed(defaultStore.makeGetterSetter('showReactionsCount'));
|
||||
const enableQuickAddMfmFunction = computed(defaultStore.makeGetterSetter('enableQuickAddMfmFunction'));
|
||||
const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
|
||||
const menuStyle = computed(defaultStore.makeGetterSetter('menuStyle'));
|
||||
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
|
||||
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
|
||||
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
|
||||
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
|
||||
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
|
||||
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
|
||||
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
|
||||
const showFixedPostFormInChannel = computed(defaultStore.makeGetterSetter('showFixedPostFormInChannel'));
|
||||
const numberOfPageCache = computed(defaultStore.makeGetterSetter('numberOfPageCache'));
|
||||
const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'));
|
||||
const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
|
||||
const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
|
||||
const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
|
||||
const showAvatarDecorations = computed(defaultStore.makeGetterSetter('showAvatarDecorations'));
|
||||
const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
|
||||
const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
|
||||
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
|
||||
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
||||
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
||||
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
||||
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
|
||||
const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
|
||||
const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer'));
|
||||
const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
|
||||
const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
|
||||
const confirmOnReact = computed(defaultStore.makeGetterSetter('confirmOnReact'));
|
||||
const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
|
||||
|
||||
watch(lang, () => {
|
||||
miLocalStorage.setItem('lang', lang.value as string);
|
||||
miLocalStorage.removeItem('locale');
|
||||
miLocalStorage.removeItem('localeVersion');
|
||||
});
|
||||
|
||||
watch(fontSize, () => {
|
||||
if (fontSize.value == null) {
|
||||
miLocalStorage.removeItem('fontSize');
|
||||
} else {
|
||||
miLocalStorage.setItem('fontSize', fontSize.value);
|
||||
}
|
||||
});
|
||||
|
||||
watch(useSystemFont, () => {
|
||||
if (useSystemFont.value) {
|
||||
miLocalStorage.setItem('useSystemFont', 't');
|
||||
} else {
|
||||
miLocalStorage.removeItem('useSystemFont');
|
||||
}
|
||||
});
|
||||
|
||||
watch([
|
||||
hemisphere,
|
||||
lang,
|
||||
fontSize,
|
||||
useSystemFont,
|
||||
enableInfiniteScroll,
|
||||
squareAvatars,
|
||||
showNoteActionsOnlyHover,
|
||||
showGapBetweenNotesInTimeline,
|
||||
instanceTicker,
|
||||
overridedDeviceKind,
|
||||
mediaListWithOneImageAppearance,
|
||||
reactionsDisplaySize,
|
||||
limitWidthOfReaction,
|
||||
highlightSensitiveMedia,
|
||||
keepScreenOn,
|
||||
disableStreamingTimeline,
|
||||
enableSeasonalScreenEffect,
|
||||
alwaysConfirmFollow,
|
||||
confirmWhenRevealingSensitiveMedia,
|
||||
contextMenu,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
||||
|
||||
function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) {
|
||||
if (langs.find(x => x[0] === targetLang)) {
|
||||
return langs.find(x => x[0] === targetLang)![1];
|
||||
} else {
|
||||
// 絵文字辞書限定の言語定義
|
||||
switch (targetLang) {
|
||||
case 'ja-JP_hira': return 'ひらがな';
|
||||
default: return targetLang;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
|
||||
async function main() {
|
||||
const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
|
||||
|
||||
function download() {
|
||||
switch (lang) {
|
||||
case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default);
|
||||
case 'ja-JP': return import('../../unicode-emoji-indexes/ja-JP.json').then(x => x.default);
|
||||
case 'ja-JP_hira': return import('../../unicode-emoji-indexes/ja-JP_hira.json').then(x => x.default);
|
||||
default: throw new Error('unrecognized lang: ' + lang);
|
||||
}
|
||||
}
|
||||
|
||||
currentIndexes[lang] = await download();
|
||||
await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
|
||||
}
|
||||
|
||||
os.promiseDialog(main());
|
||||
}
|
||||
|
||||
function removeEmojiIndex(lang: string) {
|
||||
async function main() {
|
||||
const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
|
||||
delete currentIndexes[lang];
|
||||
await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
|
||||
}
|
||||
|
||||
os.promiseDialog(main());
|
||||
}
|
||||
|
||||
async function setPinnedList() {
|
||||
const lists = await misskeyApi('users/lists/list');
|
||||
const { canceled, result: list } = await os.select({
|
||||
title: i18n.ts.selectList,
|
||||
items: lists.map(x => ({
|
||||
value: x, text: x.name,
|
||||
})),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
defaultStore.set('pinnedUserLists', [list]);
|
||||
}
|
||||
|
||||
function removePinnedList() {
|
||||
defaultStore.set('pinnedUserLists', []);
|
||||
}
|
||||
|
||||
let smashCount = 0;
|
||||
let smashTimer: number | null = null;
|
||||
|
||||
function testNotification(): void {
|
||||
const notification: Misskey.entities.Notification = {
|
||||
id: Math.random().toString(),
|
||||
createdAt: new Date().toUTCString(),
|
||||
isRead: false,
|
||||
type: 'test',
|
||||
};
|
||||
|
||||
globalEvents.emit('clientNotification', notification);
|
||||
|
||||
// セルフ通知破壊 実績関連
|
||||
smashCount++;
|
||||
if (smashCount >= 10) {
|
||||
claimAchievement('smashTestNotificationButton');
|
||||
smashCount = 0;
|
||||
}
|
||||
if (smashTimer) {
|
||||
clearTimeout(smashTimer);
|
||||
}
|
||||
smashTimer = window.setTimeout(() => {
|
||||
smashCount = 0;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function enableAllDataSaver() {
|
||||
const g = { ...defaultStore.state.dataSaver };
|
||||
|
||||
Object.keys(g).forEach((key) => { g[key] = true; });
|
||||
|
||||
dataSaver.value = g;
|
||||
}
|
||||
|
||||
function disableAllDataSaver() {
|
||||
const g = { ...defaultStore.state.dataSaver };
|
||||
|
||||
Object.keys(g).forEach((key) => { g[key] = false; });
|
||||
|
||||
dataSaver.value = g;
|
||||
}
|
||||
|
||||
watch(dataSaver, (to) => {
|
||||
defaultStore.set('dataSaver', to);
|
||||
}, {
|
||||
deep: true,
|
||||
});
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: i18n.ts.general,
|
||||
icon: 'ti ti-adjustments',
|
||||
}));
|
||||
</script>
|
@ -4,118 +4,143 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<FormSection first>
|
||||
<template #label><i class="ti ti-pencil"></i> {{ i18n.ts._exportOrImport.allNotes }}</template>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportNotes()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-star"></i> {{ i18n.ts._exportOrImport.favoritedNotes }}</template>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportFavorites()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-star"></i> {{ i18n.ts._exportOrImport.clips }}</template>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportClips()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-users"></i> {{ i18n.ts._exportOrImport.followingList }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<SearchMarker path="/settings/import-export" :label="i18n.ts.importAndExport" :keywords="['import', 'export', 'data']" icon="ti ti-package">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['notes']">
|
||||
<FormSection first>
|
||||
<template #label><i class="ti ti-pencil"></i> <SearchLabel>{{ i18n.ts._exportOrImport.allNotes }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportNotes()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['favorite', 'notes']">
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-star"></i> <SearchLabel>{{ i18n.ts._exportOrImport.favoritedNotes }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportFavorites()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['clip', 'notes']">
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-star"></i> <SearchLabel>{{ i18n.ts._exportOrImport.clips }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportClips()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['following', 'users']">
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-users"></i> <SearchLabel>{{ i18n.ts._exportOrImport.followingList }}</SearchLabel></template>
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="excludeMutingUsers">
|
||||
{{ i18n.ts._exportOrImport.excludeMutingUsers }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="excludeInactiveUsers">
|
||||
{{ i18n.ts._exportOrImport.excludeInactiveUsers }}
|
||||
</MkSwitch>
|
||||
<MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="excludeMutingUsers">
|
||||
{{ i18n.ts._exportOrImport.excludeMutingUsers }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="excludeInactiveUsers">
|
||||
{{ i18n.ts._exportOrImport.excludeInactiveUsers }}
|
||||
</MkSwitch>
|
||||
<MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkSwitch v-model="withReplies">
|
||||
{{ i18n.ts._exportOrImport.withReplies }}
|
||||
</MkSwitch>
|
||||
<MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkSwitch v-model="withReplies">
|
||||
{{ i18n.ts._exportOrImport.withReplies }}
|
||||
</MkSwitch>
|
||||
<MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-users"></i> {{ i18n.ts._exportOrImport.userLists }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-user-off"></i> {{ i18n.ts._exportOrImport.muteList }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-user-off"></i> {{ i18n.ts._exportOrImport.blockingList }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-antenna"></i> {{ i18n.ts.antennas }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['user', 'lists']">
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-users"></i> <SearchLabel>{{ i18n.ts._exportOrImport.userLists }}</SearchLabel></template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['mute', 'users']">
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-user-off"></i> <SearchLabel>{{ i18n.ts._exportOrImport.muteList }}</SearchLabel></template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['block', 'users']">
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-user-off"></i> <SearchLabel>{{ i18n.ts._exportOrImport.blockingList }}</SearchLabel></template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['antennas']">
|
||||
<FormSection>
|
||||
<template #label><i class="ti ti-antenna"></i> <SearchLabel>{{ i18n.ts.antennas }}</SearchLabel></template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="ti ti-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</MkFolder>
|
||||
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="ti ti-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div v-if="!narrow || currentPage?.route.name == null" class="nav">
|
||||
<div class="baaadecd">
|
||||
<MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
|
||||
<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
|
||||
<MkSuperMenu :def="menuDef" :grid="narrow" :searchIndex="SETTING_INDEX"></MkSuperMenu>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
|
||||
@ -29,6 +29,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
|
||||
import type { PageMetadata } from '@/scripts/page-metadata.js';
|
||||
import type { SuperMenuDef } from '@/components/MkSuperMenu.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkSuperMenu from '@/components/MkSuperMenu.vue';
|
||||
@ -38,8 +40,9 @@ import { instance } from '@/instance.js';
|
||||
import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
||||
import * as os from '@/os.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import type { PageMetadata } from '@/scripts/page-metadata.js';
|
||||
import type { SuperMenuDef } from '@/components/MkSuperMenu.vue';
|
||||
import { searchIndexes } from '@/scripts/autogen/settings-search-index.js';
|
||||
|
||||
const SETTING_INDEX = searchIndexes; // TODO: lazy load
|
||||
|
||||
const indexInfo = {
|
||||
title: i18n.ts.settings,
|
||||
@ -63,7 +66,6 @@ const ro = new ResizeObserver((entries, observer) => {
|
||||
});
|
||||
|
||||
const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||
title: i18n.ts.basicSettings,
|
||||
items: [{
|
||||
icon: 'ti ti-user',
|
||||
text: i18n.ts.profile,
|
||||
@ -101,32 +103,31 @@ const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||
active: currentPage.value?.route.name === 'security',
|
||||
}],
|
||||
}, {
|
||||
title: i18n.ts.clientSettings,
|
||||
items: [{
|
||||
icon: 'ti ti-adjustments',
|
||||
text: i18n.ts.general,
|
||||
to: '/settings/general',
|
||||
active: currentPage.value?.route.name === 'general',
|
||||
text: i18n.ts.preferences,
|
||||
to: '/settings/preferences',
|
||||
active: currentPage.value?.route.name === 'preferences',
|
||||
}, {
|
||||
icon: 'ti ti-palette',
|
||||
text: i18n.ts.theme,
|
||||
to: '/settings/theme',
|
||||
active: currentPage.value?.route.name === 'theme',
|
||||
}, {
|
||||
icon: 'ti ti-menu-2',
|
||||
text: i18n.ts.navbar,
|
||||
to: '/settings/navbar',
|
||||
active: currentPage.value?.route.name === 'navbar',
|
||||
}, {
|
||||
icon: 'ti ti-equal-double',
|
||||
text: i18n.ts.statusbar,
|
||||
to: '/settings/statusbar',
|
||||
active: currentPage.value?.route.name === 'statusbar',
|
||||
icon: 'ti ti-device-desktop',
|
||||
text: i18n.ts.appearance,
|
||||
to: '/settings/appearance',
|
||||
active: currentPage.value?.route.name === 'appearance',
|
||||
}, {
|
||||
icon: 'ti ti-music',
|
||||
text: i18n.ts.sounds,
|
||||
to: '/settings/sounds',
|
||||
active: currentPage.value?.route.name === 'sounds',
|
||||
}, {
|
||||
icon: 'ti ti-accessible',
|
||||
text: i18n.ts.accessibility,
|
||||
to: '/settings/accessibility',
|
||||
active: currentPage.value?.route.name === 'accessibility',
|
||||
}, {
|
||||
icon: 'ti ti-plug',
|
||||
text: i18n.ts.plugins,
|
||||
@ -134,7 +135,6 @@ const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||
active: currentPage.value?.route.name === 'plugin',
|
||||
}],
|
||||
}, {
|
||||
title: i18n.ts.otherSettings,
|
||||
items: [{
|
||||
icon: 'ti ti-badges',
|
||||
text: i18n.ts.roles,
|
||||
@ -160,11 +160,6 @@ const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||
text: i18n.ts.importAndExport,
|
||||
to: '/settings/import-export',
|
||||
active: currentPage.value?.route.name === 'import-export',
|
||||
}, {
|
||||
icon: 'ti ti-plane',
|
||||
text: `${i18n.ts.accountMigration}`,
|
||||
to: '/settings/migration',
|
||||
active: currentPage.value?.route.name === 'migration',
|
||||
}, {
|
||||
icon: 'ti ti-dots',
|
||||
text: i18n.ts.other,
|
||||
|
@ -68,7 +68,6 @@ import MkUserInfo from '@/components/MkUserInfo.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { signinRequired } from '@/account.js';
|
||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||
|
||||
@ -120,11 +119,6 @@ async function save(): Promise<void> {
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: i18n.ts.accountMigration,
|
||||
icon: 'ti ti-plane',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -4,132 +4,171 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-off"></i></template>
|
||||
<template #label>{{ i18n.ts.wordMute }}</template>
|
||||
<SearchMarker path="/settings/mute-block" :label="i18n.ts.muteAndBlock" icon="ti ti-ban" :keywords="['mute', 'block']">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker
|
||||
:label="i18n.ts.wordMute"
|
||||
:keywords="['note', 'word', 'soft', 'mute', 'hide']"
|
||||
>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-off"></i></template>
|
||||
<template #label>{{ i18n.ts.wordMute }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.wordMuteDescription }}</MkInfo>
|
||||
<MkSwitch v-model="showSoftWordMutedWord">{{ i18n.ts.showMutedWord }}</MkSwitch>
|
||||
<XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.wordMuteDescription }}</MkInfo>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-off"></i></template>
|
||||
<template #label>{{ i18n.ts.hardWordMute }}</template>
|
||||
<SearchMarker
|
||||
:label="i18n.ts.showMutedWord"
|
||||
:keywords="['show']"
|
||||
>
|
||||
<MkSwitch v-model="showSoftWordMutedWord">{{ i18n.ts.showMutedWord }}</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.hardWordMuteDescription }}</MkInfo>
|
||||
<XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="instance.federation !== 'none'">
|
||||
<template #icon><i class="ti ti-planet-off"></i></template>
|
||||
<template #label>{{ i18n.ts.instanceMute }}</template>
|
||||
|
||||
<XInstanceMute/>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-repeat-off"></i></template>
|
||||
<template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template>
|
||||
|
||||
<MkPagination :pagination="renoteMutingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
<XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
|
||||
</div>
|
||||
</template>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedRenoteMuteItems.includes(item.id) }]">
|
||||
<div :class="$style.userItemMain">
|
||||
<MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
|
||||
<MkUserCardMini :user="item.mutee"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.userToggle" @click="toggleRenoteMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||
<button class="_button" :class="$style.remove" @click="unrenoteMute(item.mutee, $event)"><i class="ti ti-x"></i></button>
|
||||
</div>
|
||||
<div v-if="expandedRenoteMuteItems.includes(item.id)" :class="$style.userItemSub">
|
||||
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<SearchMarker
|
||||
:label="i18n.ts.hardWordMute"
|
||||
:keywords="['note', 'word', 'hard', 'mute', 'hide']"
|
||||
>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-off"></i></template>
|
||||
<template #label>{{ i18n.ts.hardWordMute }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.hardWordMuteDescription }}</MkInfo>
|
||||
<XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkFolder>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-eye-off"></i></template>
|
||||
<template #label>{{ i18n.ts.mutedUsers }}</template>
|
||||
<SearchMarker
|
||||
:label="i18n.ts.instanceMute"
|
||||
:keywords="['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide']"
|
||||
>
|
||||
<MkFolder v-if="instance.federation !== 'none'">
|
||||
<template #icon><i class="ti ti-planet-off"></i></template>
|
||||
<template #label>{{ i18n.ts.instanceMute }}</template>
|
||||
|
||||
<MkPagination :pagination="mutingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<XInstanceMute/>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedMuteItems.includes(item.id) }]">
|
||||
<div :class="$style.userItemMain">
|
||||
<MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
|
||||
<MkUserCardMini :user="item.mutee"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||
<button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button>
|
||||
<SearchMarker
|
||||
:label="`${i18n.ts.mutedUsers} (${ i18n.ts.renote })`"
|
||||
:keywords="['renote', 'mute', 'hide', 'user']"
|
||||
>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-repeat-off"></i></template>
|
||||
<template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template>
|
||||
|
||||
<MkPagination :pagination="renoteMutingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
<div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
|
||||
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||
<div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
|
||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkFolder>
|
||||
</template>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-ban"></i></template>
|
||||
<template #label>{{ i18n.ts.blockedUsers }}</template>
|
||||
|
||||
<MkPagination :pagination="blockingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]">
|
||||
<div :class="$style.userItemMain">
|
||||
<MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)">
|
||||
<MkUserCardMini :user="item.blockee"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||
<button class="_button" :class="$style.remove" @click="unblock(item.blockee, $event)"><i class="ti ti-x"></i></button>
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedRenoteMuteItems.includes(item.id) }]">
|
||||
<div :class="$style.userItemMain">
|
||||
<MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
|
||||
<MkUserCardMini :user="item.mutee"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.userToggle" @click="toggleRenoteMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||
<button class="_button" :class="$style.remove" @click="unrenoteMute(item.mutee, $event)"><i class="ti ti-x"></i></button>
|
||||
</div>
|
||||
<div v-if="expandedRenoteMuteItems.includes(item.id)" :class="$style.userItemSub">
|
||||
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub">
|
||||
<div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||
<div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
|
||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker
|
||||
:label="i18n.ts.mutedUsers"
|
||||
:keywords="['note', 'mute', 'hide', 'user']"
|
||||
>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-eye-off"></i></template>
|
||||
<template #label>{{ i18n.ts.mutedUsers }}</template>
|
||||
|
||||
<MkPagination :pagination="mutingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedMuteItems.includes(item.id) }]">
|
||||
<div :class="$style.userItemMain">
|
||||
<MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
|
||||
<MkUserCardMini :user="item.mutee"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||
<button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button>
|
||||
</div>
|
||||
<div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
|
||||
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||
<div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
|
||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker
|
||||
:label="i18n.ts.blockedUsers"
|
||||
:keywords="['block', 'user']"
|
||||
>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-ban"></i></template>
|
||||
<template #label>{{ i18n.ts.blockedUsers }}</template>
|
||||
|
||||
<MkPagination :pagination="blockingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]">
|
||||
<div :class="$style.userItemMain">
|
||||
<MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)">
|
||||
<MkUserCardMini :user="item.blockee"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||
<button class="_button" :class="$style.remove" @click="unblock(item.blockee, $event)"><i class="ti ti-x"></i></button>
|
||||
</div>
|
||||
<div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub">
|
||||
<div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||
<div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
|
||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -4,91 +4,111 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<!--
|
||||
<MkSwitch v-model="$i.injectFeaturedNote" @update:model-value="onChangeInjectFeaturedNote">
|
||||
<template #label>{{ i18n.ts.showFeaturedNotesInTimeline }}</template>
|
||||
</MkSwitch>
|
||||
-->
|
||||
<SearchMarker path="/settings/other" :label="i18n.ts.other" :keywords="['other']" icon="ti ti-dots">
|
||||
<div class="_gaps_m">
|
||||
<!--
|
||||
<MkSwitch v-model="$i.injectFeaturedNote" @update:model-value="onChangeInjectFeaturedNote">
|
||||
<template #label>{{ i18n.ts.showFeaturedNotesInTimeline }}</template>
|
||||
</MkSwitch>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<MkSwitch v-model="reportError">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></MkSwitch>
|
||||
-->
|
||||
<!--
|
||||
<MkSwitch v-model="reportError">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></MkSwitch>
|
||||
-->
|
||||
|
||||
<FormSection first>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-info-circle"></i></template>
|
||||
<template #label>{{ i18n.ts.accountInfo }}</template>
|
||||
<FormSection first>
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['account', 'info']">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-info-circle"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.accountInfo }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkKeyValue>
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ $i.id }}</span></template>
|
||||
</MkKeyValue>
|
||||
<div class="_gaps_m">
|
||||
<MkKeyValue>
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ $i.id }}</span></template>
|
||||
</MkKeyValue>
|
||||
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.registeredDate }}</template>
|
||||
<template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.registeredDate }}</template>
|
||||
<template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-alert-triangle"></i></template>
|
||||
<template #label>{{ i18n.ts.closeAccount }}</template>
|
||||
<SearchMarker :keywords="['account', 'move', 'migration']">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-plane"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.accountMigration }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo>
|
||||
<FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo>
|
||||
<MkButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</MkButton>
|
||||
<MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<XMigration/>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-flask"></i></template>
|
||||
<template #label>{{ i18n.ts.experimentalFeatures }}</template>
|
||||
<SearchMarker :keywords="['account', 'close', 'delete']">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-alert-triangle"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.closeAccount }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="enableCondensedLine">
|
||||
<template #label>Enable condensed line</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="skipNoteRender">
|
||||
<template #label>Enable note render skipping</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<div class="_gaps_m">
|
||||
<FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo>
|
||||
<FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo>
|
||||
<MkButton v-if="!$i.isDeleted" danger @click="deleteAccount"><SearchKeyword>{{ i18n.ts._accountDelete.requestAccountDelete }}</SearchKeyword></MkButton>
|
||||
<MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-code"></i></template>
|
||||
<template #label>{{ i18n.ts.developer }}</template>
|
||||
<SearchMarker :keywords="['experimental', 'feature', 'flags']">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-flask"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.experimentalFeatures }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="devMode">
|
||||
<template #label>{{ i18n.ts.devMode }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="enableCondensedLine">
|
||||
<template #label>Enable condensed line</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="skipNoteRender">
|
||||
<template #label>Enable note render skipping</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink>
|
||||
</FormSection>
|
||||
<SearchMarker :keywords="['developer', 'mode', 'debug']">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-code"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.developer }}</SearchLabel></template>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch>
|
||||
<MkButton danger @click="updateRepliesAll(true)"><i class="ti ti-messages"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton>
|
||||
<MkButton danger @click="updateRepliesAll(false)"><i class="ti ti-messages-off"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="devMode">
|
||||
<template #label>{{ i18n.ts.devMode }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch>
|
||||
<MkButton danger @click="updateRepliesAll(true)"><i class="ti ti-messages"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton>
|
||||
<MkButton danger @click="updateRepliesAll(false)"><i class="ti ti-messages-off"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch } from 'vue';
|
||||
import XMigration from './migration.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
434
packages/frontend/src/pages/settings/preferences.vue
Normal file
434
packages/frontend/src/pages/settings/preferences.vue
Normal file
@ -0,0 +1,434 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<SearchMarker path="/settings/preferences" :label="i18n.ts.preferences" :keywords="['general', 'preferences']" icon="ti ti-adjustments">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['language']">
|
||||
<MkSelect v-model="lang">
|
||||
<template #label><SearchLabel>{{ i18n.ts.uiLanguage }}</SearchLabel></template>
|
||||
<option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option>
|
||||
<template #caption>
|
||||
<I18n :src="i18n.ts.i18nInfo" tag="span">
|
||||
<template #link>
|
||||
<MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
|
||||
</template>
|
||||
</I18n>
|
||||
</template>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop']">
|
||||
<MkRadios v-model="overridedDeviceKind">
|
||||
<template #label><SearchLabel>{{ i18n.ts.overridedDeviceKind }}</SearchLabel></template>
|
||||
<option :value="null">{{ i18n.ts.auto }}</option>
|
||||
<option value="smartphone"><i class="ti ti-device-mobile"/> {{ i18n.ts.smartphone }}</option>
|
||||
<option value="tablet"><i class="ti ti-device-tablet"/> {{ i18n.ts.tablet }}</option>
|
||||
<option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['post', 'form', 'timeline']">
|
||||
<MkSwitch v-model="showFixedPostForm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['post', 'form', 'timeline', 'channel']">
|
||||
<MkSwitch v-model="showFixedPostFormInChannel">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['pinned', 'list']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template>
|
||||
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
|
||||
<MkButton v-if="defaultStore.reactiveState.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton>
|
||||
<MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn']">
|
||||
<MkSwitch v-model="enableQuickAddMfmFunction">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<SearchMarker :keywords="['note']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.note }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['renote']">
|
||||
<MkSwitch v-model="collapseRenotes">
|
||||
<template #label><SearchLabel>{{ i18n.ts.collapseRenotes }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.collapseRenotesDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['hover', 'show', 'footer', 'action']">
|
||||
<MkSwitch v-model="showNoteActionsOnlyHover">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showNoteActionsOnlyHover }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['footer', 'action', 'clip', 'show']">
|
||||
<MkSwitch v-model="showClipButtonInNoteFooter">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showClipButtonInNoteFooter }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced']">
|
||||
<MkSwitch v-model="advancedMfm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAdvancedMfm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['reaction', 'count', 'show']">
|
||||
<MkSwitch v-model="showReactionsCount">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showReactionsCount }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment']">
|
||||
<MkSwitch v-model="loadRawImages">
|
||||
<template #label><SearchLabel>{{ i18n.ts.loadRawImages }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['notification']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.notifications }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['group']">
|
||||
<MkSwitch v-model="useGroupedNotifications">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['behavior']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.behavior }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab']">
|
||||
<MkSwitch v-model="imageNewTab">
|
||||
<template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['reaction', 'picker', 'contextmenu', 'open']">
|
||||
<MkSwitch v-model="useReactionPickerForContextMenu">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useReactionPickerForContextMenu }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['load', 'auto', 'more']">
|
||||
<MkSwitch v-model="enableInfiniteScroll">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableInfiniteScroll }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['disable', 'streaming', 'timeline']">
|
||||
<MkSwitch v-model="disableStreamingTimeline">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['follow', 'confirm', 'always']">
|
||||
<MkSwitch v-model="alwaysConfirmFollow">
|
||||
<template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm']">
|
||||
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">
|
||||
<template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['reaction', 'confirm']">
|
||||
<MkSwitch v-model="confirmOnReact">
|
||||
<template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
<SearchMarker :keywords="['server', 'disconnect', 'reconnect', 'reload', 'streaming']">
|
||||
<MkSelect v-model="serverDisconnectedBehavior">
|
||||
<template #label><SearchLabel>{{ i18n.ts.whenServerDisconnected }}</SearchLabel></template>
|
||||
<option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option>
|
||||
<option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
|
||||
<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['cache', 'page']">
|
||||
<MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing>
|
||||
<template #label><SearchLabel>{{ i18n.ts.numberOfPageCache }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
|
||||
</MkRange>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :label="i18n.ts.dataSaver" :keywords="['datasaver']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
|
||||
<MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
|
||||
</div>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="dataSaver.media">
|
||||
{{ i18n.ts._dataSaver._media.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.avatar">
|
||||
{{ i18n.ts._dataSaver._avatar.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.urlPreview">
|
||||
{{ i18n.ts._dataSaver._urlPreview.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.code">
|
||||
{{ i18n.ts._dataSaver._code.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps">
|
||||
<SearchMarker :keywords="['ad', 'show']">
|
||||
<MkSwitch v-model="forceShowAds">
|
||||
<template #label><SearchLabel>{{ i18n.ts.forceShowAds }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkRadios v-model="hemisphere">
|
||||
<template #label><SearchLabel>{{ i18n.ts.hemisphere }}</SearchLabel></template>
|
||||
<option value="N">{{ i18n.ts._hemisphere.N }}</option>
|
||||
<option value="S">{{ i18n.ts._hemisphere.S }}</option>
|
||||
<template #caption>{{ i18n.ts._hemisphere.caption }}</template>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['emoji', 'dictionary', 'additional', 'extra']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.additionalEmojiDictionary }}</SearchLabel></template>
|
||||
<div class="_buttons">
|
||||
<template v-for="lang in emojiIndexLangs" :key="lang">
|
||||
<MkButton v-if="defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton>
|
||||
<MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<FormLink to="/settings/navbar">{{ i18n.ts.navbar }}</FormLink>
|
||||
<FormLink to="/settings/statusbar">{{ i18n.ts.statusbar }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps">
|
||||
<FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { langs } from '@@/js/config.js';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
|
||||
const lang = ref(miLocalStorage.getItem('lang'));
|
||||
const dataSaver = ref(defaultStore.state.dataSaver);
|
||||
|
||||
const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
|
||||
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
|
||||
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
|
||||
const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
|
||||
const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
|
||||
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
|
||||
const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm'));
|
||||
const showReactionsCount = computed(defaultStore.makeGetterSetter('showReactionsCount'));
|
||||
const enableQuickAddMfmFunction = computed(defaultStore.makeGetterSetter('enableQuickAddMfmFunction'));
|
||||
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
|
||||
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
|
||||
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
|
||||
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
|
||||
const showFixedPostFormInChannel = computed(defaultStore.makeGetterSetter('showFixedPostFormInChannel'));
|
||||
const numberOfPageCache = computed(defaultStore.makeGetterSetter('numberOfPageCache'));
|
||||
const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
|
||||
const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
|
||||
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
||||
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
||||
const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
|
||||
const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
|
||||
const confirmOnReact = computed(defaultStore.makeGetterSetter('confirmOnReact'));
|
||||
const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
|
||||
|
||||
watch(lang, () => {
|
||||
miLocalStorage.setItem('lang', lang.value as string);
|
||||
miLocalStorage.removeItem('locale');
|
||||
miLocalStorage.removeItem('localeVersion');
|
||||
});
|
||||
|
||||
watch([
|
||||
hemisphere,
|
||||
lang,
|
||||
enableInfiniteScroll,
|
||||
showNoteActionsOnlyHover,
|
||||
overridedDeviceKind,
|
||||
disableStreamingTimeline,
|
||||
alwaysConfirmFollow,
|
||||
confirmWhenRevealingSensitiveMedia,
|
||||
contextMenu,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
||||
|
||||
function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) {
|
||||
if (langs.find(x => x[0] === targetLang)) {
|
||||
return langs.find(x => x[0] === targetLang)![1];
|
||||
} else {
|
||||
// 絵文字辞書限定の言語定義
|
||||
switch (targetLang) {
|
||||
case 'ja-JP_hira': return 'ひらがな';
|
||||
default: return targetLang;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
|
||||
async function main() {
|
||||
const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
|
||||
|
||||
function download() {
|
||||
switch (lang) {
|
||||
case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default);
|
||||
case 'ja-JP': return import('../../unicode-emoji-indexes/ja-JP.json').then(x => x.default);
|
||||
case 'ja-JP_hira': return import('../../unicode-emoji-indexes/ja-JP_hira.json').then(x => x.default);
|
||||
default: throw new Error('unrecognized lang: ' + lang);
|
||||
}
|
||||
}
|
||||
|
||||
currentIndexes[lang] = await download();
|
||||
await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
|
||||
}
|
||||
|
||||
os.promiseDialog(main());
|
||||
}
|
||||
|
||||
function removeEmojiIndex(lang: string) {
|
||||
async function main() {
|
||||
const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
|
||||
delete currentIndexes[lang];
|
||||
await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
|
||||
}
|
||||
|
||||
os.promiseDialog(main());
|
||||
}
|
||||
|
||||
async function setPinnedList() {
|
||||
const lists = await misskeyApi('users/lists/list');
|
||||
const { canceled, result: list } = await os.select({
|
||||
title: i18n.ts.selectList,
|
||||
items: lists.map(x => ({
|
||||
value: x, text: x.name,
|
||||
})),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
defaultStore.set('pinnedUserLists', [list]);
|
||||
}
|
||||
|
||||
function removePinnedList() {
|
||||
defaultStore.set('pinnedUserLists', []);
|
||||
}
|
||||
|
||||
function enableAllDataSaver() {
|
||||
const g = { ...defaultStore.state.dataSaver };
|
||||
|
||||
Object.keys(g).forEach((key) => { g[key] = true; });
|
||||
|
||||
dataSaver.value = g;
|
||||
}
|
||||
|
||||
function disableAllDataSaver() {
|
||||
const g = { ...defaultStore.state.dataSaver };
|
||||
|
||||
Object.keys(g).forEach((key) => { g[key] = false; });
|
||||
|
||||
dataSaver.value = g;
|
||||
}
|
||||
|
||||
watch(dataSaver, (to) => {
|
||||
defaultStore.set('dataSaver', to);
|
||||
}, {
|
||||
deep: true,
|
||||
});
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: i18n.ts.general,
|
||||
icon: 'ti ti-adjustments',
|
||||
}));
|
||||
</script>
|
@ -4,158 +4,208 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="isLocked" @update:modelValue="save()">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch>
|
||||
<MkSwitch v-if="isLocked" v-model="autoAcceptFollowed" @update:modelValue="save()">{{ i18n.ts.autoAcceptFollowed }}</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="publicReactions" @update:modelValue="save()">
|
||||
{{ i18n.ts.makeReactionsPublic }}
|
||||
<template #caption>{{ i18n.ts.makeReactionsPublicDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSelect v-model="followingVisibility" @update:modelValue="save()">
|
||||
<template #label>{{ i18n.ts.followingVisibility }}</template>
|
||||
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
||||
<option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
|
||||
<option value="private">{{ i18n.ts._ffVisibility.private }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSelect v-model="followersVisibility" @update:modelValue="save()">
|
||||
<template #label>{{ i18n.ts.followersVisibility }}</template>
|
||||
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
||||
<option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
|
||||
<option value="private">{{ i18n.ts._ffVisibility.private }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSwitch v-model="hideOnlineStatus" @update:modelValue="save()">
|
||||
{{ i18n.ts.hideOnlineStatus }}
|
||||
<template #caption>{{ i18n.ts.hideOnlineStatusDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="noCrawle" @update:modelValue="save()">
|
||||
{{ i18n.ts.noCrawle }}
|
||||
<template #caption>{{ i18n.ts.noCrawleDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="preventAiLearning" @update:modelValue="save()">
|
||||
{{ i18n.ts.preventAiLearning }}
|
||||
<template #caption>{{ i18n.ts.preventAiLearningDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="isExplorable" @update:modelValue="save()">
|
||||
{{ i18n.ts.makeExplorable }}
|
||||
<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.lockdown }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch :modelValue="requireSigninToViewContents" @update:modelValue="update_requireSigninToViewContents">
|
||||
{{ i18n.ts._accountSettings.requireSigninToViewContents }}
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
|
||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div>
|
||||
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
|
||||
</template>
|
||||
<SearchMarker path="/settings/privacy" :label="i18n.ts.privacy" :keywords="['privacy']" icon="ti ti-lock-open">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['follow', 'lock']">
|
||||
<MkSwitch v-model="isLocked" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.makeFollowManuallyApprove }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.lockedAccountInfo }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSlot>
|
||||
<template #label>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBefore }}</template>
|
||||
<MkDisableSection :disabled="!isLocked">
|
||||
<SearchMarker :keywords="['follow', 'auto', 'accept']">
|
||||
<MkSwitch v-model="autoAcceptFollowed" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.autoAcceptFollowed }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</MkDisableSection>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkSelect :modelValue="makeNotesFollowersOnlyBefore_type" @update:modelValue="makeNotesFollowersOnlyBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null">
|
||||
<option :value="null">{{ i18n.ts.none }}</option>
|
||||
<option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option>
|
||||
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
||||
</MkSelect>
|
||||
<SearchMarker :keywords="['reaction', 'public']">
|
||||
<MkSwitch v-model="publicReactions" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.makeReactionsPublic }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.makeReactionsPublicDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<MkSelect v-if="makeNotesFollowersOnlyBefore_type === 'relative'" v-model="makeNotesFollowersOnlyBefore">
|
||||
<option :value="-3600">{{ i18n.ts.oneHour }}</option>
|
||||
<option :value="-86400">{{ i18n.ts.oneDay }}</option>
|
||||
<option :value="-259200">{{ i18n.ts.threeDays }}</option>
|
||||
<option :value="-604800">{{ i18n.ts.oneWeek }}</option>
|
||||
<option :value="-2592000">{{ i18n.ts.oneMonth }}</option>
|
||||
<option :value="-7776000">{{ i18n.ts.threeMonths }}</option>
|
||||
<option :value="-31104000">{{ i18n.ts.oneYear }}</option>
|
||||
</MkSelect>
|
||||
<SearchMarker :keywords="['following', 'visibility']">
|
||||
<MkSelect v-model="followingVisibility" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.followingVisibility }}</SearchLabel></template>
|
||||
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
||||
<option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
|
||||
<option value="private">{{ i18n.ts._ffVisibility.private }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
|
||||
<MkInput
|
||||
v-if="makeNotesFollowersOnlyBefore_type === 'absolute'"
|
||||
:modelValue="formatDateTimeString(new Date(makeNotesFollowersOnlyBefore * 1000), 'yyyy-MM-dd')"
|
||||
type="date"
|
||||
:manualSave="true"
|
||||
@update:modelValue="makeNotesFollowersOnlyBefore = Math.floor(new Date($event).getTime() / 1000)"
|
||||
>
|
||||
</MkInput>
|
||||
</div>
|
||||
<SearchMarker :keywords="['follower', 'visibility']">
|
||||
<MkSelect v-model="followersVisibility" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.followersVisibility }}</SearchLabel></template>
|
||||
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
||||
<option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
|
||||
<option value="private">{{ i18n.ts._ffVisibility.private }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</div>
|
||||
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
|
||||
</template>
|
||||
</FormSlot>
|
||||
<SearchMarker :keywords="['online', 'status']">
|
||||
<MkSwitch v-model="hideOnlineStatus" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.hideOnlineStatus }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.hideOnlineStatusDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSlot>
|
||||
<template #label>{{ i18n.ts._accountSettings.makeNotesHiddenBefore }}</template>
|
||||
<SearchMarker :keywords="['crawle', 'index', 'search']">
|
||||
<MkSwitch v-model="noCrawle" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.noCrawle }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.noCrawleDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkSelect :modelValue="makeNotesHiddenBefore_type" @update:modelValue="makeNotesHiddenBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null">
|
||||
<option :value="null">{{ i18n.ts.none }}</option>
|
||||
<option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option>
|
||||
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
||||
</MkSelect>
|
||||
<SearchMarker :keywords="['crawle', 'ai']">
|
||||
<MkSwitch v-model="preventAiLearning" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.preventAiLearning }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.preventAiLearningDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<MkSelect v-if="makeNotesHiddenBefore_type === 'relative'" v-model="makeNotesHiddenBefore">
|
||||
<option :value="-3600">{{ i18n.ts.oneHour }}</option>
|
||||
<option :value="-86400">{{ i18n.ts.oneDay }}</option>
|
||||
<option :value="-259200">{{ i18n.ts.threeDays }}</option>
|
||||
<option :value="-604800">{{ i18n.ts.oneWeek }}</option>
|
||||
<option :value="-2592000">{{ i18n.ts.oneMonth }}</option>
|
||||
<option :value="-7776000">{{ i18n.ts.threeMonths }}</option>
|
||||
<option :value="-31104000">{{ i18n.ts.oneYear }}</option>
|
||||
</MkSelect>
|
||||
<SearchMarker :keywords="['explore']">
|
||||
<MkSwitch v-model="isExplorable" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.makeExplorable }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.makeExplorableDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<MkInput
|
||||
v-if="makeNotesHiddenBefore_type === 'absolute'"
|
||||
:modelValue="formatDateTimeString(new Date(makeNotesHiddenBefore * 1000), 'yyyy-MM-dd')"
|
||||
type="date"
|
||||
:manualSave="true"
|
||||
@update:modelValue="makeNotesHiddenBefore = Math.floor(new Date($event).getTime() / 1000)"
|
||||
>
|
||||
</MkInput>
|
||||
</div>
|
||||
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</div>
|
||||
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
|
||||
</template>
|
||||
</FormSlot>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
|
||||
<MkFolder v-if="!rememberNoteVisibility">
|
||||
<template #label>{{ i18n.ts.defaultNoteVisibility }}</template>
|
||||
<template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template>
|
||||
<SearchMarker :keywords="['lockdown']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.lockdown }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSelect v-model="defaultNoteVisibility">
|
||||
<option value="public">{{ i18n.ts._visibility.public }}</option>
|
||||
<option value="home">{{ i18n.ts._visibility.home }}</option>
|
||||
<option value="followers">{{ i18n.ts._visibility.followers }}</option>
|
||||
<option value="specified">{{ i18n.ts._visibility.specified }}</option>
|
||||
</MkSelect>
|
||||
<MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
<SearchMarker :keywords="['login', 'signin']">
|
||||
<MkSwitch :modelValue="requireSigninToViewContents" @update:modelValue="update_requireSigninToViewContents">
|
||||
<template #label><SearchLabel>{{ i18n.ts._accountSettings.requireSigninToViewContents }}</SearchLabel></template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
|
||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div>
|
||||
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<MkSwitch v-model="keepCw" @update:modelValue="save()">{{ i18n.ts.keepCw }}</MkSwitch>
|
||||
</div>
|
||||
<SearchMarker :keywords="['follower']">
|
||||
<FormSlot>
|
||||
<template #label><SearchLabel>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBefore }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkSelect :modelValue="makeNotesFollowersOnlyBefore_type" @update:modelValue="makeNotesFollowersOnlyBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null">
|
||||
<option :value="null">{{ i18n.ts.none }}</option>
|
||||
<option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option>
|
||||
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSelect v-if="makeNotesFollowersOnlyBefore_type === 'relative'" v-model="makeNotesFollowersOnlyBefore">
|
||||
<option :value="-3600">{{ i18n.ts.oneHour }}</option>
|
||||
<option :value="-86400">{{ i18n.ts.oneDay }}</option>
|
||||
<option :value="-259200">{{ i18n.ts.threeDays }}</option>
|
||||
<option :value="-604800">{{ i18n.ts.oneWeek }}</option>
|
||||
<option :value="-2592000">{{ i18n.ts.oneMonth }}</option>
|
||||
<option :value="-7776000">{{ i18n.ts.threeMonths }}</option>
|
||||
<option :value="-31104000">{{ i18n.ts.oneYear }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkInput
|
||||
v-if="makeNotesFollowersOnlyBefore_type === 'absolute'"
|
||||
:modelValue="formatDateTimeString(new Date(makeNotesFollowersOnlyBefore * 1000), 'yyyy-MM-dd')"
|
||||
type="date"
|
||||
:manualSave="true"
|
||||
@update:modelValue="makeNotesFollowersOnlyBefore = Math.floor(new Date($event).getTime() / 1000)"
|
||||
>
|
||||
</MkInput>
|
||||
</div>
|
||||
|
||||
<template #caption>
|
||||
<div><SearchKeyword>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</SearchKeyword></div>
|
||||
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
|
||||
</template>
|
||||
</FormSlot>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['hidden']">
|
||||
<FormSlot>
|
||||
<template #label><SearchLabel>{{ i18n.ts._accountSettings.makeNotesHiddenBefore }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkSelect :modelValue="makeNotesHiddenBefore_type" @update:modelValue="makeNotesHiddenBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null">
|
||||
<option :value="null">{{ i18n.ts.none }}</option>
|
||||
<option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option>
|
||||
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSelect v-if="makeNotesHiddenBefore_type === 'relative'" v-model="makeNotesHiddenBefore">
|
||||
<option :value="-3600">{{ i18n.ts.oneHour }}</option>
|
||||
<option :value="-86400">{{ i18n.ts.oneDay }}</option>
|
||||
<option :value="-259200">{{ i18n.ts.threeDays }}</option>
|
||||
<option :value="-604800">{{ i18n.ts.oneWeek }}</option>
|
||||
<option :value="-2592000">{{ i18n.ts.oneMonth }}</option>
|
||||
<option :value="-7776000">{{ i18n.ts.threeMonths }}</option>
|
||||
<option :value="-31104000">{{ i18n.ts.oneYear }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkInput
|
||||
v-if="makeNotesHiddenBefore_type === 'absolute'"
|
||||
:modelValue="formatDateTimeString(new Date(makeNotesHiddenBefore * 1000), 'yyyy-MM-dd')"
|
||||
type="date"
|
||||
:manualSave="true"
|
||||
@update:modelValue="makeNotesHiddenBefore = Math.floor(new Date($event).getTime() / 1000)"
|
||||
>
|
||||
</MkInput>
|
||||
</div>
|
||||
|
||||
<template #caption>
|
||||
<div><SearchKeyword>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</SearchKeyword></div>
|
||||
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
|
||||
</template>
|
||||
</FormSlot>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['remember', 'keep', 'note', 'visibility']">
|
||||
<MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['default', 'note', 'visibility']">
|
||||
<MkFolder v-if="!rememberNoteVisibility">
|
||||
<template #label><SearchLabel>{{ i18n.ts.defaultNoteVisibility }}</SearchLabel></template>
|
||||
<template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSelect v-model="defaultNoteVisibility">
|
||||
<option value="public">{{ i18n.ts._visibility.public }}</option>
|
||||
<option value="home">{{ i18n.ts._visibility.home }}</option>
|
||||
<option value="followers">{{ i18n.ts._visibility.followers }}</option>
|
||||
<option value="specified">{{ i18n.ts._visibility.specified }}</option>
|
||||
</MkSelect>
|
||||
<MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<SearchMarker :keywords="['remember', 'keep', 'note', 'cw']">
|
||||
<MkSwitch v-model="keepCw" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepCw }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -174,6 +224,7 @@ import FormSlot from '@/components/form/slot.vue';
|
||||
import { formatDateTimeString } from '@/scripts/format-time-string.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import * as os from '@/os.js';
|
||||
import MkDisableSection from '@/components/MkDisableSection.vue';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
|
@ -4,115 +4,152 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<div class="_panel">
|
||||
<div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
||||
</div>
|
||||
<div :class="$style.avatarContainer">
|
||||
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/>
|
||||
<div class="_buttonsCenter">
|
||||
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
||||
<MkButton primary rounded link to="/settings/avatar-decoration">{{ i18n.ts.decorate }} <i class="ti ti-sparkles"></i></MkButton>
|
||||
<SearchMarker path="/settings/profile" :label="i18n.ts.profile" :keywords="['profile']" icon="ti ti-user">
|
||||
<div class="_gaps_m">
|
||||
<div class="_panel">
|
||||
<div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||
<div :class="$style.bannerEdit">
|
||||
<SearchMarker :keywords="['banner', 'change']">
|
||||
<MkButton primary rounded @click="changeBanner"><SearchLabel>{{ i18n.ts._profile.changeBanner }}</SearchLabel></MkButton>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.avatarContainer">
|
||||
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/>
|
||||
<div class="_buttonsCenter">
|
||||
<SearchMarker :keywords="['avatar', 'icon', 'change']">
|
||||
<MkButton primary rounded @click="changeAvatar"><SearchLabel>{{ i18n.ts._profile.changeAvatar }}</SearchLabel></MkButton>
|
||||
</SearchMarker>
|
||||
<MkButton primary rounded link to="/settings/avatar-decoration">{{ i18n.ts.decorate }} <i class="ti ti-sparkles"></i></MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
|
||||
<template #label>{{ i18n.ts._profile.name }}</template>
|
||||
</MkInput>
|
||||
<SearchMarker :keywords="['name']">
|
||||
<MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
|
||||
<template #label><SearchLabel>{{ i18n.ts._profile.name }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<MkTextarea v-model="profile.description" :max="500" tall manualSave mfmAutocomplete :mfmPreview="true">
|
||||
<template #label>{{ i18n.ts._profile.description }}</template>
|
||||
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
|
||||
</MkTextarea>
|
||||
<SearchMarker :keywords="['description', 'bio']">
|
||||
<MkTextarea v-model="profile.description" :max="500" tall manualSave mfmAutocomplete :mfmPreview="true">
|
||||
<template #label><SearchLabel>{{ i18n.ts._profile.description }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
|
||||
</MkTextarea>
|
||||
</SearchMarker>
|
||||
|
||||
<MkInput v-model="profile.location" manualSave>
|
||||
<template #label>{{ i18n.ts.location }}</template>
|
||||
<template #prefix><i class="ti ti-map-pin"></i></template>
|
||||
</MkInput>
|
||||
<SearchMarker :keywords="['location', 'locale']">
|
||||
<MkInput v-model="profile.location" manualSave>
|
||||
<template #label><SearchLabel>{{ i18n.ts.location }}</SearchLabel></template>
|
||||
<template #prefix><i class="ti ti-map-pin"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<MkInput v-model="profile.birthday" type="date" manualSave>
|
||||
<template #label>{{ i18n.ts.birthday }}</template>
|
||||
<template #prefix><i class="ti ti-cake"></i></template>
|
||||
</MkInput>
|
||||
<SearchMarker :keywords="['birthday', 'birthdate', 'age']">
|
||||
<MkInput v-model="profile.birthday" type="date" manualSave>
|
||||
<template #label><SearchLabel>{{ i18n.ts.birthday }}</SearchLabel></template>
|
||||
<template #prefix><i class="ti ti-cake"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<MkSelect v-model="profile.lang">
|
||||
<template #label>{{ i18n.ts.language }}</template>
|
||||
<option v-for="x in Object.keys(langmap)" :key="x" :value="x">{{ langmap[x].nativeName }}</option>
|
||||
</MkSelect>
|
||||
<SearchMarker :keywords="['language', 'locale']">
|
||||
<MkSelect v-model="profile.lang">
|
||||
<template #label><SearchLabel>{{ i18n.ts.language }}</SearchLabel></template>
|
||||
<option v-for="x in Object.keys(langmap)" :key="x" :value="x">{{ langmap[x].nativeName }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSlot>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-list"></i></template>
|
||||
<template #label>{{ i18n.ts._profile.metadataEdit }}</template>
|
||||
<template #footer>
|
||||
<div class="_buttons">
|
||||
<MkButton primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton :disabled="fields.length >= 16" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
<MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" danger @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
<MkButton v-else @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div :class="$style.metadataRoot" class="_gaps_s">
|
||||
<MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo>
|
||||
|
||||
<Sortable
|
||||
v-model="fields"
|
||||
class="_gaps_s"
|
||||
itemKey="id"
|
||||
:animation="150"
|
||||
:handle="'.' + $style.dragItemHandle"
|
||||
@start="e => e.item.classList.add('active')"
|
||||
@end="e => e.item.classList.remove('active')"
|
||||
>
|
||||
<template #item="{element, index}">
|
||||
<div v-panel :class="$style.fieldDragItem">
|
||||
<button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
|
||||
<button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button>
|
||||
<div :class="$style.dragItemForm">
|
||||
<FormSplit :minWidth="200">
|
||||
<MkInput v-model="element.name" small :placeholder="i18n.ts._profile.metadataLabel">
|
||||
</MkInput>
|
||||
<MkInput v-model="element.value" small :placeholder="i18n.ts._profile.metadataContent">
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
</div>
|
||||
<SearchMarker :keywords="['metadata']">
|
||||
<FormSlot>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-list"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._profile.metadataEdit }}</SearchLabel></template>
|
||||
<template #footer>
|
||||
<div class="_buttons">
|
||||
<MkButton primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton :disabled="fields.length >= 16" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
<MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" danger @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
<MkButton v-else @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
</Sortable>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
||||
</FormSlot>
|
||||
|
||||
<MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false">
|
||||
<template #label>{{ i18n.ts._profile.followedMessage }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._profile.followedMessageDescription }}</div>
|
||||
<div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div>
|
||||
</template>
|
||||
</MkInput>
|
||||
<div :class="$style.metadataRoot" class="_gaps_s">
|
||||
<MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo>
|
||||
|
||||
<MkSelect v-model="reactionAcceptance">
|
||||
<template #label>{{ i18n.ts.reactionAcceptance }}</template>
|
||||
<option :value="null">{{ i18n.ts.all }}</option>
|
||||
<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
|
||||
<option value="nonSensitiveOnly">{{ i18n.ts.nonSensitiveOnly }}</option>
|
||||
<option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
|
||||
<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
|
||||
</MkSelect>
|
||||
<Sortable
|
||||
v-model="fields"
|
||||
class="_gaps_s"
|
||||
itemKey="id"
|
||||
:animation="150"
|
||||
:handle="'.' + $style.dragItemHandle"
|
||||
@start="e => e.item.classList.add('active')"
|
||||
@end="e => e.item.classList.remove('active')"
|
||||
>
|
||||
<template #item="{element, index}">
|
||||
<div v-panel :class="$style.fieldDragItem">
|
||||
<button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
|
||||
<button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button>
|
||||
<div :class="$style.dragItemForm">
|
||||
<FormSplit :minWidth="200">
|
||||
<MkInput v-model="element.name" small :placeholder="i18n.ts._profile.metadataLabel">
|
||||
</MkInput>
|
||||
<MkInput v-model="element.value" small :placeholder="i18n.ts._profile.metadataContent">
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Sortable>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
||||
</FormSlot>
|
||||
</SearchMarker>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
||||
<SearchMarker :keywords="['follow', 'message']">
|
||||
<MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false">
|
||||
<template #label><SearchLabel>{{ i18n.ts._profile.followedMessage }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption>
|
||||
<div><SearchKeyword>{{ i18n.ts._profile.followedMessageDescription }}</SearchKeyword></div>
|
||||
<div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div>
|
||||
</template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch>
|
||||
<MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
<SearchMarker :keywords="['reaction']">
|
||||
<MkSelect v-model="reactionAcceptance">
|
||||
<template #label><SearchLabel>{{ i18n.ts.reactionAcceptance }}</SearchLabel></template>
|
||||
<option :value="null">{{ i18n.ts.all }}</option>
|
||||
<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
|
||||
<option value="nonSensitiveOnly">{{ i18n.ts.nonSensitiveOnly }}</option>
|
||||
<option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
|
||||
<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.advancedSettings }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['cat']">
|
||||
<MkSwitch v-model="profile.isCat">
|
||||
<template #label><SearchLabel>{{ i18n.ts.flagAsCat }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.flagAsCatDescription }}</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['bot']">
|
||||
<MkSwitch v-model="profile.isBot">
|
||||
<template #label><SearchLabel>{{ i18n.ts.flagAsBot }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.flagAsBotDescription }}</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -4,39 +4,48 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<FormSection first>
|
||||
<template #label>{{ i18n.ts.password }}</template>
|
||||
<MkButton primary @click="change()">{{ i18n.ts.changePassword }}</MkButton>
|
||||
</FormSection>
|
||||
<SearchMarker path="/settings/security" :label="i18n.ts.security" :keywords="['security']" icon="ti ti-lock" :inlining="['2fa']">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['password']">
|
||||
<FormSection first>
|
||||
<template #label><SearchLabel>{{ i18n.ts.password }}</SearchLabel></template>
|
||||
|
||||
<X2fa/>
|
||||
<SearchMarker>
|
||||
<MkButton primary @click="change()">
|
||||
<SearchLabel>{{ i18n.ts.changePassword }}</SearchLabel>
|
||||
</MkButton>
|
||||
</SearchMarker>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.signinHistory }}</template>
|
||||
<MkPagination :pagination="pagination" disableAutoLoad>
|
||||
<template #default="{items}">
|
||||
<div>
|
||||
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
|
||||
<header>
|
||||
<i v-if="item.success" class="ti ti-check icon succ"></i>
|
||||
<i v-else class="ti ti-circle-x icon fail"></i>
|
||||
<code class="ip _monospace">{{ item.ip }}</code>
|
||||
<MkTime :time="item.createdAt" class="time"/>
|
||||
</header>
|
||||
<X2fa/>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.signinHistory }}</template>
|
||||
<MkPagination :pagination="pagination" disableAutoLoad>
|
||||
<template #default="{items}">
|
||||
<div>
|
||||
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
|
||||
<header>
|
||||
<i v-if="item.success" class="ti ti-check icon succ"></i>
|
||||
<i v-else class="ti ti-circle-x icon fail"></i>
|
||||
<code class="ip _monospace">{{ item.ip }}</code>
|
||||
<MkTime :time="item.createdAt" class="time"/>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</FormSection>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<FormSlot>
|
||||
<MkButton danger @click="regenerateToken"><i class="ti ti-refresh"></i> {{ i18n.ts.regenerateLoginToken }}</MkButton>
|
||||
<template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template>
|
||||
</FormSlot>
|
||||
</FormSection>
|
||||
</div>
|
||||
<FormSection>
|
||||
<FormSlot>
|
||||
<MkButton danger @click="regenerateToken"><i class="ti ti-refresh"></i> {{ i18n.ts.regenerateLoginToken }}</MkButton>
|
||||
<template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template>
|
||||
</FormSlot>
|
||||
</FormSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -4,43 +4,53 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="notUseSound">
|
||||
<template #label>{{ i18n.ts.notUseSound }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="useSoundOnlyWhenActive">
|
||||
<template #label>{{ i18n.ts.useSoundOnlyWhenActive }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`">
|
||||
<template #label>{{ i18n.ts.masterVolume }}</template>
|
||||
</MkRange>
|
||||
<SearchMarker path="/settings/sounds" :label="i18n.ts.sounds" :keywords="['sounds']" icon="ti ti-music">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['mute']">
|
||||
<MkSwitch v-model="notUseSound">
|
||||
<template #label><SearchLabel>{{ i18n.ts.notUseSound }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.sounds }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder v-for="type in operationTypes" :key="type">
|
||||
<template #label>{{ i18n.ts._sfx[type] }}</template>
|
||||
<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<MkLoading/>
|
||||
</template>
|
||||
</Suspense>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
<SearchMarker :keywords="['active', 'mute']">
|
||||
<MkSwitch v-model="useSoundOnlyWhenActive">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useSoundOnlyWhenActive }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<MkButton danger @click="reset()"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
|
||||
</div>
|
||||
<SearchMarker :keywords="['volume', 'master']">
|
||||
<MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`">
|
||||
<template #label><SearchLabel>{{ i18n.ts.masterVolume }}</SearchLabel></template>
|
||||
</MkRange>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.sounds }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder v-for="type in operationTypes" :key="type">
|
||||
<template #label>{{ i18n.ts._sfx[type] }}</template>
|
||||
<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<MkLoading/>
|
||||
</template>
|
||||
</Suspense>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<MkButton danger @click="reset()"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import XSound from './sounds.sound.vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type { SoundType, OperationType } from '@/scripts/sound.js';
|
||||
import type { SoundStore } from '@/store.js';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
|
@ -4,56 +4,72 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m rsljpzjq">
|
||||
<div v-adaptive-border class="rfqxtzch _panel">
|
||||
<div class="toggle">
|
||||
<div class="toggleWrapper">
|
||||
<input id="dn" v-model="darkMode" type="checkbox" class="dn"/>
|
||||
<label for="dn" class="toggle">
|
||||
<span class="before">{{ i18n.ts.light }}</span>
|
||||
<span class="after">{{ i18n.ts.dark }}</span>
|
||||
<span class="toggle__handler">
|
||||
<span class="crater crater--1"></span>
|
||||
<span class="crater crater--2"></span>
|
||||
<span class="crater crater--3"></span>
|
||||
</span>
|
||||
<span class="star star--1"></span>
|
||||
<span class="star star--2"></span>
|
||||
<span class="star star--3"></span>
|
||||
<span class="star star--4"></span>
|
||||
<span class="star star--5"></span>
|
||||
<span class="star star--6"></span>
|
||||
</label>
|
||||
<SearchMarker path="/settings/theme" :label="i18n.ts.theme" :keywords="['theme']" icon="ti ti-palette">
|
||||
<div class="_gaps_m rsljpzjq">
|
||||
<div v-adaptive-border class="rfqxtzch _panel">
|
||||
<div class="toggle">
|
||||
<div class="toggleWrapper">
|
||||
<input id="dn" v-model="darkMode" type="checkbox" class="dn"/>
|
||||
<label for="dn" class="toggle">
|
||||
<span class="before">{{ i18n.ts.light }}</span>
|
||||
<span class="after">{{ i18n.ts.dark }}</span>
|
||||
<span class="toggle__handler">
|
||||
<span class="crater crater--1"></span>
|
||||
<span class="crater crater--2"></span>
|
||||
<span class="crater crater--3"></span>
|
||||
</span>
|
||||
<span class="star star--1"></span>
|
||||
<span class="star star--2"></span>
|
||||
<span class="star star--3"></span>
|
||||
<span class="star star--4"></span>
|
||||
<span class="star star--5"></span>
|
||||
<span class="star star--6"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sync">
|
||||
<SearchMarker :keywords="['sync', 'device', 'dark', 'light', 'mode']">
|
||||
<MkSwitch v-model="syncDeviceDarkMode">
|
||||
<template #label><SearchLabel>{{ i18n.ts.syncDeviceDarkMode }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sync">
|
||||
<MkSwitch v-model="syncDeviceDarkMode">{{ i18n.ts.syncDeviceDarkMode }}</MkSwitch>
|
||||
|
||||
<div class="selects">
|
||||
<div class="select">
|
||||
<SearchMarker :keywords="['light', 'theme']">
|
||||
<MkSelect v-model="lightThemeId" large :items="lightThemeSelectorItems">
|
||||
<template #label><SearchLabel>{{ i18n.ts.themeForLightMode }}</SearchLabel></template>
|
||||
<template #prefix><i class="ti ti-sun"></i></template>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
<div class="select">
|
||||
<SearchMarker :keywords="['dark', 'theme']">
|
||||
<MkSelect v-model="darkThemeId" large :items="darkThemeSelectorItems">
|
||||
<template #label><SearchLabel>{{ i18n.ts.themeForDarkMode }}</SearchLabel></template>
|
||||
<template #prefix><i class="ti ti-moon"></i></template>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormSection>
|
||||
<div class="_formLinksGrid">
|
||||
<FormLink to="/settings/theme/manage"><template #icon><i class="ti ti-tool"></i></template>{{ i18n.ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink>
|
||||
<FormLink to="https://assets.misskey.io/theme/list" external><template #icon><i class="ti ti-world"></i></template>{{ i18n.ts._theme.explore }}</FormLink>
|
||||
<FormLink to="/settings/theme/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._theme.install }}</FormLink>
|
||||
<FormLink to="/theme-editor"><template #icon><i class="ti ti-paint"></i></template>{{ i18n.ts._theme.make }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<SearchMarker :keywords="['wallpaper']">
|
||||
<MkButton v-if="wallpaper == null" @click="setWallpaper"><SearchLabel>{{ i18n.ts.setWallpaper }}</SearchLabel></MkButton>
|
||||
<MkButton v-else @click="wallpaper = null">{{ i18n.ts.removeWallpaper }}</MkButton>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
<div class="selects">
|
||||
<MkSelect v-model="lightThemeId" large class="select" :items="lightThemeSelectorItems">
|
||||
<template #label>{{ i18n.ts.themeForLightMode }}</template>
|
||||
<template #prefix><i class="ti ti-sun"></i></template>
|
||||
</MkSelect>
|
||||
<MkSelect v-model="darkThemeId" large class="select" :items="darkThemeSelectorItems">
|
||||
<template #label>{{ i18n.ts.themeForDarkMode }}</template>
|
||||
<template #prefix><i class="ti ti-moon"></i></template>
|
||||
</MkSelect>
|
||||
</div>
|
||||
|
||||
<FormSection>
|
||||
<div class="_formLinksGrid">
|
||||
<FormLink to="/settings/theme/manage"><template #icon><i class="ti ti-tool"></i></template>{{ i18n.ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink>
|
||||
<FormLink to="https://assets.misskey.io/theme/list" external><template #icon><i class="ti ti-world"></i></template>{{ i18n.ts._theme.explore }}</FormLink>
|
||||
<FormLink to="/settings/theme/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._theme.install }}</FormLink>
|
||||
<FormLink to="/theme-editor"><template #icon><i class="ti ti-paint"></i></template>{{ i18n.ts._theme.make }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<MkButton v-if="wallpaper == null" @click="setWallpaper">{{ i18n.ts.setWallpaper }}</MkButton>
|
||||
<MkButton v-else @click="wallpaper = null">{{ i18n.ts.removeWallpaper }}</MkButton>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -90,9 +90,9 @@ const routes: RouteDef[] = [{
|
||||
name: 'security',
|
||||
component: page(() => import('@/pages/settings/security.vue')),
|
||||
}, {
|
||||
path: '/general',
|
||||
name: 'general',
|
||||
component: page(() => import('@/pages/settings/general.vue')),
|
||||
path: '/preferences',
|
||||
name: 'preferences',
|
||||
component: page(() => import('@/pages/settings/preferences.vue')),
|
||||
}, {
|
||||
path: '/theme/install',
|
||||
name: 'theme',
|
||||
@ -105,6 +105,10 @@ const routes: RouteDef[] = [{
|
||||
path: '/theme',
|
||||
name: 'theme',
|
||||
component: page(() => import('@/pages/settings/theme.vue')),
|
||||
}, {
|
||||
path: '/appearance',
|
||||
name: 'appearance',
|
||||
component: page(() => import('@/pages/settings/appearance.vue')),
|
||||
}, {
|
||||
path: '/navbar',
|
||||
name: 'navbar',
|
||||
@ -117,6 +121,10 @@ const routes: RouteDef[] = [{
|
||||
path: '/sounds',
|
||||
name: 'sounds',
|
||||
component: page(() => import('@/pages/settings/sounds.vue')),
|
||||
}, {
|
||||
path: '/accessibility',
|
||||
name: 'accessibility',
|
||||
component: page(() => import('@/pages/settings/accessibility.vue')),
|
||||
}, {
|
||||
path: '/plugin/install',
|
||||
name: 'plugin',
|
||||
@ -161,13 +169,9 @@ const routes: RouteDef[] = [{
|
||||
path: '/preferences-backups',
|
||||
name: 'preferences-backups',
|
||||
component: page(() => import('@/pages/settings/preferences-backups.vue')),
|
||||
}, {
|
||||
path: '/migration',
|
||||
name: 'migration',
|
||||
component: page(() => import('@/pages/settings/migration.vue')),
|
||||
}, {
|
||||
path: '/custom-css',
|
||||
name: 'general',
|
||||
name: 'preferences',
|
||||
component: page(() => import('@/pages/settings/custom-css.vue')),
|
||||
}, {
|
||||
path: '/accounts',
|
||||
|
815
packages/frontend/src/scripts/autogen/settings-search-index.ts
Normal file
815
packages/frontend/src/scripts/autogen/settings-search-index.ts
Normal file
@ -0,0 +1,815 @@
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// This file was automatically generated by create-search-index.
|
||||
// Do not edit this file.
|
||||
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export type SearchIndexItem = {
|
||||
id: string;
|
||||
path?: string;
|
||||
label: string;
|
||||
keywords: string[];
|
||||
icon?: string;
|
||||
children?: SearchIndexItem[];
|
||||
};
|
||||
|
||||
export const searchIndexes: SearchIndexItem[] = [
|
||||
{
|
||||
id: 'flXd1LC7r',
|
||||
children: [
|
||||
{
|
||||
id: 'hB11H5oul',
|
||||
label: i18n.ts.syncDeviceDarkMode,
|
||||
keywords: ['sync', 'device', 'dark', 'light', 'mode'],
|
||||
},
|
||||
{
|
||||
id: 'fDbLtIKeo',
|
||||
label: i18n.ts.themeForLightMode,
|
||||
keywords: ['light', 'theme'],
|
||||
},
|
||||
{
|
||||
id: 'eLOwK5Ia2',
|
||||
label: i18n.ts.themeForDarkMode,
|
||||
keywords: ['dark', 'theme'],
|
||||
},
|
||||
{
|
||||
id: 'ujvMfyzUr',
|
||||
label: i18n.ts.setWallpaper,
|
||||
keywords: ['wallpaper'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.theme,
|
||||
keywords: ['theme'],
|
||||
path: '/settings/theme',
|
||||
icon: 'ti ti-palette',
|
||||
},
|
||||
{
|
||||
id: '6fFIRXUww',
|
||||
children: [
|
||||
{
|
||||
id: 'nO7NnzqiC',
|
||||
label: i18n.ts.notUseSound,
|
||||
keywords: ['mute'],
|
||||
},
|
||||
{
|
||||
id: 'xy5OOBB4A',
|
||||
label: i18n.ts.useSoundOnlyWhenActive,
|
||||
keywords: ['active', 'mute'],
|
||||
},
|
||||
{
|
||||
id: '9MxYVIf7k',
|
||||
label: i18n.ts.masterVolume,
|
||||
keywords: ['volume', 'master'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.sounds,
|
||||
keywords: ['sounds'],
|
||||
path: '/settings/sounds',
|
||||
icon: 'ti ti-music',
|
||||
},
|
||||
{
|
||||
id: '5BjnxMfYV',
|
||||
children: [
|
||||
{
|
||||
id: '3UqdSCaFw',
|
||||
children: [
|
||||
{
|
||||
id: '75QPEg57v',
|
||||
label: i18n.ts.changePassword,
|
||||
keywords: [],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.password,
|
||||
keywords: ['password'],
|
||||
},
|
||||
{
|
||||
id: '2fa',
|
||||
children: [
|
||||
{
|
||||
id: 'qCXM0HtJ7',
|
||||
label: i18n.ts.totp,
|
||||
keywords: ['totp', 'app', i18n.ts.totpDescription],
|
||||
},
|
||||
{
|
||||
id: '3g1RePuD9',
|
||||
label: i18n.ts.securityKeyAndPasskey,
|
||||
keywords: ['security', 'key', 'passkey'],
|
||||
},
|
||||
{
|
||||
id: 'pFRud5u8k',
|
||||
label: i18n.ts.passwordLessLogin,
|
||||
keywords: ['password', 'less', 'key', 'passkey', 'login', 'signin', i18n.ts.passwordLessLoginDescription],
|
||||
},
|
||||
],
|
||||
label: i18n.ts['2fa'],
|
||||
keywords: ['2fa'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.security,
|
||||
keywords: ['security'],
|
||||
path: '/settings/security',
|
||||
icon: 'ti ti-lock',
|
||||
},
|
||||
{
|
||||
id: 'w4L6myH61',
|
||||
children: [
|
||||
{
|
||||
id: 'ru8DrOn3J',
|
||||
label: i18n.ts._profile.changeBanner,
|
||||
keywords: ['banner', 'change'],
|
||||
},
|
||||
{
|
||||
id: 'CCnD8Apnu',
|
||||
label: i18n.ts._profile.changeAvatar,
|
||||
keywords: ['avatar', 'icon', 'change'],
|
||||
},
|
||||
{
|
||||
id: 'yFEVCJxFX',
|
||||
label: i18n.ts._profile.name,
|
||||
keywords: ['name'],
|
||||
},
|
||||
{
|
||||
id: '2O1S5reaB',
|
||||
label: i18n.ts._profile.description,
|
||||
keywords: ['description', 'bio'],
|
||||
},
|
||||
{
|
||||
id: 'pWi4OLS8g',
|
||||
label: i18n.ts.location,
|
||||
keywords: ['location', 'locale'],
|
||||
},
|
||||
{
|
||||
id: 'oLO5X6Wtw',
|
||||
label: i18n.ts.birthday,
|
||||
keywords: ['birthday', 'birthdate', 'age'],
|
||||
},
|
||||
{
|
||||
id: 'm2trKwPgq',
|
||||
label: i18n.ts.language,
|
||||
keywords: ['language', 'locale'],
|
||||
},
|
||||
{
|
||||
id: 'kfDZxCDp9',
|
||||
label: i18n.ts._profile.metadataEdit,
|
||||
keywords: ['metadata'],
|
||||
},
|
||||
{
|
||||
id: 'uPt3MFymp',
|
||||
label: i18n.ts._profile.followedMessage,
|
||||
keywords: ['follow', 'message', i18n.ts._profile.followedMessageDescription],
|
||||
},
|
||||
{
|
||||
id: 'wuGg0tBjw',
|
||||
label: i18n.ts.reactionAcceptance,
|
||||
keywords: ['reaction'],
|
||||
},
|
||||
{
|
||||
id: 'EezPpmMnf',
|
||||
children: [
|
||||
{
|
||||
id: 'f2cRLh8ad',
|
||||
label: i18n.ts.flagAsCat,
|
||||
keywords: ['cat'],
|
||||
},
|
||||
{
|
||||
id: 'eVoViiF3h',
|
||||
label: i18n.ts.flagAsBot,
|
||||
keywords: ['bot'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.advancedSettings,
|
||||
keywords: [],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.profile,
|
||||
keywords: ['profile'],
|
||||
path: '/settings/profile',
|
||||
icon: 'ti ti-user',
|
||||
},
|
||||
{
|
||||
id: '2rp9ka5Ht',
|
||||
children: [
|
||||
{
|
||||
id: 'qBUSKPxLW',
|
||||
label: i18n.ts.makeFollowManuallyApprove,
|
||||
keywords: ['follow', 'lock', i18n.ts.lockedAccountInfo],
|
||||
},
|
||||
{
|
||||
id: '3LZBlZCej',
|
||||
label: i18n.ts.autoAcceptFollowed,
|
||||
keywords: ['follow', 'auto', 'accept'],
|
||||
},
|
||||
{
|
||||
id: '9gOp28wKG',
|
||||
label: i18n.ts.makeReactionsPublic,
|
||||
keywords: ['reaction', 'public', i18n.ts.makeReactionsPublicDescription],
|
||||
},
|
||||
{
|
||||
id: 'CjAkqMhct',
|
||||
label: i18n.ts.followingVisibility,
|
||||
keywords: ['following', 'visibility'],
|
||||
},
|
||||
{
|
||||
id: '4nEwI6LYt',
|
||||
label: i18n.ts.followersVisibility,
|
||||
keywords: ['follower', 'visibility'],
|
||||
},
|
||||
{
|
||||
id: 'naMp37wTL',
|
||||
label: i18n.ts.hideOnlineStatus,
|
||||
keywords: ['online', 'status', i18n.ts.hideOnlineStatusDescription],
|
||||
},
|
||||
{
|
||||
id: 'p0dCVR0UP',
|
||||
label: i18n.ts.noCrawle,
|
||||
keywords: ['crawle', 'index', 'search', i18n.ts.noCrawleDescription],
|
||||
},
|
||||
{
|
||||
id: 'aceURmNPq',
|
||||
label: i18n.ts.preventAiLearning,
|
||||
keywords: ['crawle', 'ai', i18n.ts.preventAiLearningDescription],
|
||||
},
|
||||
{
|
||||
id: 'ahABA0j7u',
|
||||
label: i18n.ts.makeExplorable,
|
||||
keywords: ['explore', i18n.ts.makeExplorableDescription],
|
||||
},
|
||||
{
|
||||
id: 'cyeDbLN8N',
|
||||
children: [
|
||||
{
|
||||
id: 'xEYlOghao',
|
||||
label: i18n.ts._accountSettings.requireSigninToViewContents,
|
||||
keywords: ['login', 'signin'],
|
||||
},
|
||||
{
|
||||
id: 'sMmYFCS60',
|
||||
label: i18n.ts._accountSettings.makeNotesFollowersOnlyBefore,
|
||||
keywords: ['follower', i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription],
|
||||
},
|
||||
{
|
||||
id: '2prkeWRSd',
|
||||
label: i18n.ts._accountSettings.makeNotesHiddenBefore,
|
||||
keywords: ['hidden', i18n.ts._accountSettings.makeNotesHiddenBeforeDescription],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.lockdown,
|
||||
keywords: ['lockdown'],
|
||||
},
|
||||
{
|
||||
id: '37QLEyrtk',
|
||||
label: i18n.ts.rememberNoteVisibility,
|
||||
keywords: ['remember', 'keep', 'note', 'visibility'],
|
||||
},
|
||||
{
|
||||
id: 'rhKwScbVS',
|
||||
label: i18n.ts.defaultNoteVisibility,
|
||||
keywords: ['default', 'note', 'visibility'],
|
||||
},
|
||||
{
|
||||
id: '3EmXVyevo',
|
||||
label: i18n.ts.keepCw,
|
||||
keywords: ['remember', 'keep', 'note', 'cw'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.privacy,
|
||||
keywords: ['privacy'],
|
||||
path: '/settings/privacy',
|
||||
icon: 'ti ti-lock-open',
|
||||
},
|
||||
{
|
||||
id: '3yCAv0IsZ',
|
||||
children: [
|
||||
{
|
||||
id: 'x1GWSQnPw',
|
||||
label: i18n.ts.uiLanguage,
|
||||
keywords: ['language'],
|
||||
},
|
||||
{
|
||||
id: 'EOSa4rtt3',
|
||||
label: i18n.ts.overridedDeviceKind,
|
||||
keywords: ['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop'],
|
||||
},
|
||||
{
|
||||
id: 'm9LhX8BG8',
|
||||
label: i18n.ts.showFixedPostForm,
|
||||
keywords: ['post', 'form', 'timeline'],
|
||||
},
|
||||
{
|
||||
id: '9ra14w32V',
|
||||
label: i18n.ts.showFixedPostFormInChannel,
|
||||
keywords: ['post', 'form', 'timeline', 'channel'],
|
||||
},
|
||||
{
|
||||
id: '84MdeDWL1',
|
||||
label: i18n.ts.pinnedList,
|
||||
keywords: ['pinned', 'list'],
|
||||
},
|
||||
{
|
||||
id: 'fYdWhBbrN',
|
||||
label: i18n.ts.enableQuickAddMfmFunction,
|
||||
keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'],
|
||||
},
|
||||
{
|
||||
id: '4huRldNp5',
|
||||
children: [
|
||||
{
|
||||
id: 'puIqj1a8b',
|
||||
label: i18n.ts.collapseRenotes,
|
||||
keywords: ['renote', i18n.ts.collapseRenotesDescription],
|
||||
},
|
||||
{
|
||||
id: 'wqpOC22Zm',
|
||||
label: i18n.ts.showNoteActionsOnlyHover,
|
||||
keywords: ['hover', 'show', 'footer', 'action'],
|
||||
},
|
||||
{
|
||||
id: 'cjfAtxMzP',
|
||||
label: i18n.ts.showClipButtonInNoteFooter,
|
||||
keywords: ['footer', 'action', 'clip', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'khzxoCjtp',
|
||||
label: i18n.ts.enableAdvancedMfm,
|
||||
keywords: ['mfm', 'enable', 'show', 'advanced'],
|
||||
},
|
||||
{
|
||||
id: 'uJkoVjTmF',
|
||||
label: i18n.ts.showReactionsCount,
|
||||
keywords: ['reaction', 'count', 'show'],
|
||||
},
|
||||
{
|
||||
id: '9gTCaLkIf',
|
||||
label: i18n.ts.loadRawImages,
|
||||
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.note,
|
||||
keywords: ['note'],
|
||||
},
|
||||
{
|
||||
id: '5G6O6qdis',
|
||||
children: [
|
||||
{
|
||||
id: 'sYTvqUbhP',
|
||||
label: i18n.ts.useGroupedNotifications,
|
||||
keywords: ['group'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.notifications,
|
||||
keywords: ['notification'],
|
||||
},
|
||||
{
|
||||
id: 'c3xhLyXZ5',
|
||||
children: [
|
||||
{
|
||||
id: 'FbhoeuRAD',
|
||||
label: i18n.ts.openImageInNewTab,
|
||||
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'],
|
||||
},
|
||||
{
|
||||
id: 'qixh85g2N',
|
||||
label: i18n.ts.useReactionPickerForContextMenu,
|
||||
keywords: ['reaction', 'picker', 'contextmenu', 'open'],
|
||||
},
|
||||
{
|
||||
id: 'd2H4E5ys6',
|
||||
label: i18n.ts.enableInfiniteScroll,
|
||||
keywords: ['load', 'auto', 'more'],
|
||||
},
|
||||
{
|
||||
id: 'jC7LtTnmc',
|
||||
label: i18n.ts.disableStreamingTimeline,
|
||||
keywords: ['disable', 'streaming', 'timeline'],
|
||||
},
|
||||
{
|
||||
id: '8xazEqlgZ',
|
||||
label: i18n.ts.alwaysConfirmFollow,
|
||||
keywords: ['follow', 'confirm', 'always'],
|
||||
},
|
||||
{
|
||||
id: 'wZqrDQZar',
|
||||
label: i18n.ts.confirmWhenRevealingSensitiveMedia,
|
||||
keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'],
|
||||
},
|
||||
{
|
||||
id: '5QTUzrpT3',
|
||||
label: i18n.ts.confirmOnReact,
|
||||
keywords: ['reaction', 'confirm'],
|
||||
},
|
||||
{
|
||||
id: 'nygexkaUk',
|
||||
label: i18n.ts.whenServerDisconnected,
|
||||
keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'],
|
||||
},
|
||||
{
|
||||
id: 'whKYKvaQB',
|
||||
label: i18n.ts.numberOfPageCache,
|
||||
keywords: ['cache', 'page'],
|
||||
},
|
||||
{
|
||||
id: 'lBbtAg0Hm',
|
||||
label: i18n.ts.dataSaver,
|
||||
keywords: ['datasaver'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.behavior,
|
||||
keywords: ['behavior'],
|
||||
},
|
||||
{
|
||||
id: 'y2v7CV9zs',
|
||||
children: [
|
||||
{
|
||||
id: 'k1qTdyfzM',
|
||||
label: i18n.ts.forceShowAds,
|
||||
keywords: ['ad', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'e9As4Us48',
|
||||
label: i18n.ts.hemisphere,
|
||||
keywords: [],
|
||||
},
|
||||
{
|
||||
id: 'zvM13vl26',
|
||||
label: i18n.ts.additionalEmojiDictionary,
|
||||
keywords: ['emoji', 'dictionary', 'additional', 'extra'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.other,
|
||||
keywords: [],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.preferences,
|
||||
keywords: ['general', 'preferences'],
|
||||
path: '/settings/preferences',
|
||||
icon: 'ti ti-adjustments',
|
||||
},
|
||||
{
|
||||
id: 'F1uK9ssiY',
|
||||
children: [
|
||||
{
|
||||
id: 'msAcN6u3S',
|
||||
label: i18n.ts.accountInfo,
|
||||
keywords: ['account', 'info'],
|
||||
},
|
||||
{
|
||||
id: 'ts8DgdnZV',
|
||||
label: i18n.ts.accountMigration,
|
||||
keywords: ['account', 'move', 'migration'],
|
||||
},
|
||||
{
|
||||
id: '4BG7nBECm',
|
||||
label: i18n.ts.closeAccount,
|
||||
keywords: ['account', 'close', 'delete', i18n.ts._accountDelete.requestAccountDelete],
|
||||
},
|
||||
{
|
||||
id: '2qI6ruPgi',
|
||||
label: i18n.ts.experimentalFeatures,
|
||||
keywords: ['experimental', 'feature', 'flags'],
|
||||
},
|
||||
{
|
||||
id: 'cIeaax47o',
|
||||
label: i18n.ts.developer,
|
||||
keywords: ['developer', 'mode', 'debug'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.other,
|
||||
keywords: ['other'],
|
||||
path: '/settings/other',
|
||||
icon: 'ti ti-dots',
|
||||
},
|
||||
{
|
||||
id: '3icEvyv2D',
|
||||
children: [
|
||||
{
|
||||
id: 'Tyt3gZTy',
|
||||
children: [
|
||||
{
|
||||
id: '9b7ZURyAt',
|
||||
label: i18n.ts.showMutedWord,
|
||||
keywords: ['show'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.wordMute,
|
||||
keywords: ['note', 'word', 'soft', 'mute', 'hide'],
|
||||
},
|
||||
{
|
||||
id: 'kdMk41II0',
|
||||
label: i18n.ts.hardWordMute,
|
||||
keywords: ['note', 'word', 'hard', 'mute', 'hide'],
|
||||
},
|
||||
{
|
||||
id: 'mjORQamAK',
|
||||
label: i18n.ts.instanceMute,
|
||||
keywords: ['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide'],
|
||||
},
|
||||
{
|
||||
id: '1ZT7S9FZd',
|
||||
label: `${i18n.ts.mutedUsers} (${ i18n.ts.renote })`,
|
||||
keywords: ['renote', 'mute', 'hide', 'user'],
|
||||
},
|
||||
{
|
||||
id: 'ANrPit3kQ',
|
||||
label: i18n.ts.mutedUsers,
|
||||
keywords: ['note', 'mute', 'hide', 'user'],
|
||||
},
|
||||
{
|
||||
id: 'bPAE4lfno',
|
||||
label: i18n.ts.blockedUsers,
|
||||
keywords: ['block', 'user'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.muteAndBlock,
|
||||
keywords: ['mute', 'block'],
|
||||
path: '/settings/mute-block',
|
||||
icon: 'ti ti-ban',
|
||||
},
|
||||
{
|
||||
id: 'qE2vLlMkF',
|
||||
children: [
|
||||
{
|
||||
id: 'hPPEzjvZC',
|
||||
label: i18n.ts._exportOrImport.allNotes,
|
||||
keywords: ['notes'],
|
||||
},
|
||||
{
|
||||
id: 'AFaeHsCUB',
|
||||
label: i18n.ts._exportOrImport.favoritedNotes,
|
||||
keywords: ['favorite', 'notes'],
|
||||
},
|
||||
{
|
||||
id: 'xyCPmQiRo',
|
||||
label: i18n.ts._exportOrImport.clips,
|
||||
keywords: ['clip', 'notes'],
|
||||
},
|
||||
{
|
||||
id: 'Ch7hWAGUy',
|
||||
label: i18n.ts._exportOrImport.followingList,
|
||||
keywords: ['following', 'users'],
|
||||
},
|
||||
{
|
||||
id: 'AwPgFboEx',
|
||||
label: i18n.ts._exportOrImport.userLists,
|
||||
keywords: ['user', 'lists'],
|
||||
},
|
||||
{
|
||||
id: 'nporiHshC',
|
||||
label: i18n.ts._exportOrImport.muteList,
|
||||
keywords: ['mute', 'users'],
|
||||
},
|
||||
{
|
||||
id: 'BsCzR7vNw',
|
||||
label: i18n.ts._exportOrImport.blockingList,
|
||||
keywords: ['block', 'users'],
|
||||
},
|
||||
{
|
||||
id: 'dvf4IgYrQ',
|
||||
label: i18n.ts.antennas,
|
||||
keywords: ['antennas'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.importAndExport,
|
||||
keywords: ['import', 'export', 'data'],
|
||||
path: '/settings/import-export',
|
||||
icon: 'ti ti-package',
|
||||
},
|
||||
{
|
||||
id: '3Tcxw4Fwl',
|
||||
children: [
|
||||
{
|
||||
id: 'iIai9O65I',
|
||||
label: i18n.ts.emailAddress,
|
||||
keywords: ['email', 'address'],
|
||||
},
|
||||
{
|
||||
id: 'i6cC6oi0m',
|
||||
label: i18n.ts.receiveAnnouncementFromInstance,
|
||||
keywords: ['announcement', 'email'],
|
||||
},
|
||||
{
|
||||
id: 'C1YTinP11',
|
||||
label: i18n.ts.emailNotification,
|
||||
keywords: ['notification', 'email'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.email,
|
||||
keywords: ['email'],
|
||||
path: '/settings/email',
|
||||
icon: 'ti ti-mail',
|
||||
},
|
||||
{
|
||||
id: 'tnYoppRiv',
|
||||
children: [
|
||||
{
|
||||
id: 'ncIq6TAR2',
|
||||
label: i18n.ts.usageAmount,
|
||||
keywords: ['capacity', 'usage'],
|
||||
},
|
||||
{
|
||||
id: '2c4CQSvSr',
|
||||
label: i18n.ts.statistics,
|
||||
keywords: ['statistics', 'usage'],
|
||||
},
|
||||
{
|
||||
id: 'pepHELHMt',
|
||||
label: i18n.ts.uploadFolder,
|
||||
keywords: ['default', 'upload', 'folder'],
|
||||
},
|
||||
{
|
||||
id: 'xqOWrABxV',
|
||||
label: i18n.ts.keepOriginalUploading,
|
||||
keywords: ['keep', 'original', 'raw', 'upload', i18n.ts.keepOriginalUploadingDescription],
|
||||
},
|
||||
{
|
||||
id: 'oqUiI5w0s',
|
||||
label: i18n.ts.keepOriginalFilename,
|
||||
keywords: ['keep', 'original', 'filename', i18n.ts.keepOriginalFilenameDescription],
|
||||
},
|
||||
{
|
||||
id: 'Aszkikq9n',
|
||||
label: i18n.ts.alwaysMarkSensitive,
|
||||
keywords: ['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file'],
|
||||
},
|
||||
{
|
||||
id: 'iGlVjsfVj',
|
||||
label: i18n.ts.enableAutoSensitive,
|
||||
keywords: ['auto', 'nsfw', 'sensitive', 'media', 'file', i18n.ts.enableAutoSensitiveDescription],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.drive,
|
||||
keywords: ['drive'],
|
||||
path: '/settings/drive',
|
||||
icon: 'ti ti-cloud',
|
||||
},
|
||||
{
|
||||
id: 'gtaOSdIJB',
|
||||
label: i18n.ts.avatarDecorations,
|
||||
keywords: ['avatar', 'icon', 'decoration'],
|
||||
path: '/settings/avatar-decoration',
|
||||
icon: 'ti ti-sparkles',
|
||||
},
|
||||
{
|
||||
id: 'AqPvMgn3A',
|
||||
children: [
|
||||
{
|
||||
id: 'j5gTtuMWP',
|
||||
label: i18n.ts.useBlurEffect,
|
||||
keywords: ['blur'],
|
||||
},
|
||||
{
|
||||
id: 'vbZvyLDC1',
|
||||
label: i18n.ts.useBlurEffectForModal,
|
||||
keywords: ['blur', 'modal'],
|
||||
},
|
||||
{
|
||||
id: '6fLNMTwNt',
|
||||
label: i18n.ts.highlightSensitiveMedia,
|
||||
keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'],
|
||||
},
|
||||
{
|
||||
id: 'hhvF8Z4pF',
|
||||
label: i18n.ts.squareAvatars,
|
||||
keywords: ['avatar', 'icon', 'square'],
|
||||
},
|
||||
{
|
||||
id: 'DsS2CwjYE',
|
||||
label: i18n.ts.showAvatarDecorations,
|
||||
keywords: ['avatar', 'icon', 'decoration', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'pWZ0ypy2g',
|
||||
label: i18n.ts.showGapBetweenNotesInTimeline,
|
||||
keywords: ['note', 'timeline', 'gap'],
|
||||
},
|
||||
{
|
||||
id: 'AfRMcC6IM',
|
||||
label: i18n.ts.useSystemFont,
|
||||
keywords: ['font', 'system', 'native'],
|
||||
},
|
||||
{
|
||||
id: 'jD0qbxlzN',
|
||||
label: i18n.ts.seasonalScreenEffect,
|
||||
keywords: ['effect', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'EdYo3hOK',
|
||||
label: i18n.ts.menuStyle,
|
||||
keywords: ['menu', 'style', 'popup', 'drawer'],
|
||||
},
|
||||
{
|
||||
id: '9mSlX0EkD',
|
||||
label: i18n.ts.emojiStyle,
|
||||
keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'],
|
||||
},
|
||||
{
|
||||
id: '44UmMwmUe',
|
||||
label: i18n.ts.fontSize,
|
||||
keywords: ['font', 'size'],
|
||||
},
|
||||
{
|
||||
id: 'vFB0pLzck',
|
||||
children: [
|
||||
{
|
||||
id: 'pc7IpPEU4',
|
||||
label: i18n.ts.reactionsDisplaySize,
|
||||
keywords: ['reaction', 'size', 'scale', 'display'],
|
||||
},
|
||||
{
|
||||
id: 'siOW5aSwp',
|
||||
label: i18n.ts.limitWidthOfReaction,
|
||||
keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'],
|
||||
},
|
||||
{
|
||||
id: 'dDUvhk13F',
|
||||
label: i18n.ts.mediaListWithOneImageAppearance,
|
||||
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'],
|
||||
},
|
||||
{
|
||||
id: 'CLxNL1Rp0',
|
||||
label: i18n.ts.instanceTicker,
|
||||
keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'],
|
||||
},
|
||||
{
|
||||
id: 'dP2KWDYzD',
|
||||
label: i18n.ts.displayOfSensitiveMedia,
|
||||
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.displayOfNote,
|
||||
keywords: ['note', 'display'],
|
||||
},
|
||||
{
|
||||
id: 'dVOzi22IW',
|
||||
children: [
|
||||
{
|
||||
id: 'aoF4ufUwn',
|
||||
label: i18n.ts.position,
|
||||
keywords: ['position'],
|
||||
},
|
||||
{
|
||||
id: 'sKK2XSS69',
|
||||
label: i18n.ts.stackAxis,
|
||||
keywords: ['stack', 'axis', 'direction'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.notificationDisplay,
|
||||
keywords: ['notification', 'display'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.appearance,
|
||||
keywords: ['appearance'],
|
||||
path: '/settings/appearance',
|
||||
icon: 'ti ti-device-desktop',
|
||||
},
|
||||
{
|
||||
id: 'f08Mi1Uwn',
|
||||
children: [
|
||||
{
|
||||
id: '7ov7ceoij',
|
||||
label: i18n.ts.reduceUiAnimation,
|
||||
keywords: ['animation', 'motion', 'reduce'],
|
||||
},
|
||||
{
|
||||
id: 'RhYwm8At',
|
||||
label: i18n.ts.disableShowingAnimatedImages,
|
||||
keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'],
|
||||
},
|
||||
{
|
||||
id: '5mZxz2cru',
|
||||
label: i18n.ts.enableAnimatedMfm,
|
||||
keywords: ['mfm', 'enable', 'show', 'animated'],
|
||||
},
|
||||
{
|
||||
id: 'bgjamYEis',
|
||||
label: i18n.ts.enableHorizontalSwipe,
|
||||
keywords: ['swipe', 'horizontal', 'tab'],
|
||||
},
|
||||
{
|
||||
id: 'yPEpJigqY',
|
||||
label: i18n.ts.keepScreenOn,
|
||||
keywords: ['keep', 'screen', 'display', 'on'],
|
||||
},
|
||||
{
|
||||
id: 'oxwiGKMu0',
|
||||
label: i18n.ts.useNativeUIForVideoAudioPlayer,
|
||||
keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
|
||||
},
|
||||
{
|
||||
id: 'n90tffyiU',
|
||||
label: i18n.ts._contextMenu.title,
|
||||
keywords: ['contextmenu', 'system', 'native'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.accessibility,
|
||||
keywords: ['accessibility'],
|
||||
path: '/settings/accessibility',
|
||||
icon: 'ti ti-accessible',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type SearchIndex = typeof searchIndexes;
|
@ -56,21 +56,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="!forceIconOnly" class="_button" :class="$style.toggleButton" @click="toggleIconOnly">
|
||||
<!--
|
||||
<svg viewBox="0 0 16 48" :class="$style.toggleButtonShape">
|
||||
<g transform="matrix(0.333333,0,0,0.222222,0.000895785,13.3333)">
|
||||
<path d="M23.935,-24C37.223,-24 47.995,-7.842 47.995,12.09C47.995,34.077 47.995,62.07 47.995,84.034C47.995,93.573 45.469,102.721 40.972,109.466C36.475,116.211 30.377,120 24.018,120L23.997,120C10.743,120 -0.003,136.118 -0.003,156C-0.003,156 -0.003,156 -0.003,156L-0.003,-60L-0.003,-59.901C-0.003,-50.379 2.519,-41.248 7.007,-34.515C11.496,-27.782 17.584,-24 23.931,-24C23.932,-24 23.934,-24 23.935,-24Z" style="fill:var(--MI_THEME-navBg);"/>
|
||||
</g>
|
||||
</svg>
|
||||
-->
|
||||
<svg viewBox="0 0 16 64" :class="$style.toggleButtonShape">
|
||||
<g transform="matrix(0.333333,0,0,0.222222,0.000895785,21.3333)">
|
||||
<path d="M47.488,7.995C47.79,10.11 47.943,12.266 47.943,14.429C47.997,26.989 47.997,84 47.997,84C47.997,84 44.018,118.246 23.997,133.5C-0.374,152.07 -0.003,192 -0.003,192L-0.003,-96C-0.003,-96 0.151,-56.216 23.997,-37.5C40.861,-24.265 46.043,-1.243 47.488,7.995Z" style="fill:var(--MI_THEME-navBg);"/>
|
||||
</g>
|
||||
</svg>
|
||||
<i :class="'ti ' + `ti-chevron-${ iconOnly ? 'right' : 'left' }`" style="font-size: 12px; margin-left: -8px;"></i>
|
||||
</button>
|
||||
|
||||
<!--
|
||||
<svg viewBox="0 0 16 48" :class="$style.subButtonShape">
|
||||
<g transform="matrix(0.333333,0,0,0.222222,0.000895785,13.3333)">
|
||||
<path d="M23.935,-24C37.223,-24 47.995,-7.842 47.995,12.09C47.995,34.077 47.995,62.07 47.995,84.034C47.995,93.573 45.469,102.721 40.972,109.466C36.475,116.211 30.377,120 24.018,120L23.997,120C10.743,120 -0.003,136.118 -0.003,156C-0.003,156 -0.003,156 -0.003,156L-0.003,-60L-0.003,-59.901C-0.003,-50.379 2.519,-41.248 7.007,-34.515C11.496,-27.782 17.584,-24 23.931,-24C23.932,-24 23.934,-24 23.935,-24Z" style="fill:var(--MI_THEME-navBg);"/>
|
||||
</g>
|
||||
</svg>
|
||||
-->
|
||||
|
||||
<div :class="$style.subButtons">
|
||||
<div :class="[$style.subButton, $style.menuEditButton]">
|
||||
<svg viewBox="0 0 16 64" :class="$style.subButtonShape">
|
||||
<g transform="matrix(0.333333,0,0,0.222222,0.000895785,21.3333)">
|
||||
<path d="M47.488,7.995C47.79,10.11 47.943,12.266 47.943,14.429C47.997,26.989 47.997,84 47.997,84C47.997,84 44.018,118.246 23.997,133.5C-0.374,152.07 -0.003,192 -0.003,192L-0.003,-96C-0.003,-96 0.151,-56.216 23.997,-37.5C40.861,-24.265 46.043,-1.243 47.488,7.995Z" style="fill:var(--MI_THEME-navBg);"/>
|
||||
</g>
|
||||
</svg>
|
||||
<button class="_button" :class="$style.subButtonClickable" @click="menuEdit"><i :class="$style.subButtonIcon" class="ti ti-settings-2"></i></button>
|
||||
</div>
|
||||
<div v-if="!forceIconOnly" :class="$style.subButtonGapFill"></div>
|
||||
<div v-if="!forceIconOnly" :class="$style.subButtonGapFillDivider"></div>
|
||||
<div v-if="!forceIconOnly" :class="[$style.subButton, $style.toggleButton]">
|
||||
<svg viewBox="0 0 16 64" :class="$style.subButtonShape">
|
||||
<g transform="matrix(0.333333,0,0,0.222222,0.000895785,21.3333)">
|
||||
<path d="M47.488,7.995C47.79,10.11 47.943,12.266 47.943,14.429C47.997,26.989 47.997,84 47.997,84C47.997,84 44.018,118.246 23.997,133.5C-0.374,152.07 -0.003,192 -0.003,192L-0.003,-96C-0.003,-96 0.151,-56.216 23.997,-37.5C40.861,-24.265 46.043,-1.243 47.488,7.995Z" style="fill:var(--MI_THEME-navBg);"/>
|
||||
</g>
|
||||
</svg>
|
||||
<button class="_button" :class="$style.subButtonClickable" @click="toggleIconOnly"><i v-if="iconOnly" class="ti ti-chevron-right" :class="$style.subButtonIcon"></i><i v-else class="ti ti-chevron-left" :class="$style.subButtonIcon"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -84,6 +98,9 @@ import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const forceIconOnly = ref(window.innerWidth <= 1279);
|
||||
const iconOnly = computed(() => {
|
||||
@ -128,6 +145,10 @@ function more(ev: MouseEvent) {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
function menuEdit() {
|
||||
router.push('/settings/navbar');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
@ -136,6 +157,8 @@ function more(ev: MouseEvent) {
|
||||
--nav-icon-only-width: 80px;
|
||||
--nav-bg-transparent: color(from var(--MI_THEME-navBg) srgb r g b / 0.5);
|
||||
|
||||
--subButtonWidth: 20px;
|
||||
|
||||
flex: 0 0 var(--nav-width);
|
||||
width: var(--nav-width);
|
||||
box-sizing: border-box;
|
||||
@ -171,23 +194,80 @@ function more(ev: MouseEvent) {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.toggleButton {
|
||||
.subButtons {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: var(--nav-width);
|
||||
bottom: 80px;
|
||||
z-index: 1001;
|
||||
width: 16px;
|
||||
height: 64px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.toggleButtonShape {
|
||||
.subButton {
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 1002;
|
||||
width: var(--subButtonWidth);
|
||||
height: 50px;
|
||||
box-sizing: border-box;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.subButtonShape {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 16px;
|
||||
margin: auto;
|
||||
width: var(--subButtonWidth);
|
||||
height: calc(var(--subButtonWidth) * 4);
|
||||
}
|
||||
|
||||
.subButtonClickable {
|
||||
position: absolute;
|
||||
display: block;
|
||||
max-width: unset;
|
||||
width: 24px;
|
||||
height: 42px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -4px;
|
||||
margin: auto;
|
||||
font-size: 10px;
|
||||
|
||||
&:hover {
|
||||
color: var(--MI_THEME-fgHighlighted);
|
||||
|
||||
.subButtonIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subButtonIcon {
|
||||
margin-left: -4px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.subButtonGapFill {
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
width: var(--subButtonWidth);
|
||||
height: 64px;
|
||||
margin-top: -32px;
|
||||
margin-bottom: -32px;
|
||||
pointer-events: none;
|
||||
background: var(--MI_THEME-navBg);
|
||||
}
|
||||
|
||||
.subButtonGapFillDivider {
|
||||
position: relative;
|
||||
z-index: 1010;
|
||||
margin-left: -2px;
|
||||
width: 14px;
|
||||
height: 1px;
|
||||
background: var(--MI_THEME-divider);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.root:not(.iconOnly) {
|
||||
@ -419,7 +499,7 @@ function more(ev: MouseEvent) {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.toggleButton {
|
||||
.subButtons {
|
||||
left: var(--nav-width);
|
||||
}
|
||||
}
|
||||
@ -623,7 +703,7 @@ function more(ev: MouseEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
.toggleButton {
|
||||
.subButtons {
|
||||
left: var(--nav-icon-only-width);
|
||||
}
|
||||
}
|
||||
|
@ -96,12 +96,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { instanceName } from '@@/js/config.js';
|
||||
import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js';
|
||||
import { isLink } from '@@/js/is-link.js';
|
||||
import XCommon from './_common_/common.vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type MkStickyContainer from '@/components/global/MkStickyContainer.vue';
|
||||
import type { PageMetadata } from '@/scripts/page-metadata.js';
|
||||
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
@ -109,7 +110,6 @@ import { navbarItemDef } from '@/navbar.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
||||
import type { PageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { useScrollPositionManager } from '@/nirax.js';
|
||||
@ -331,6 +331,8 @@ $widgets-hide-threshold: 1090px;
|
||||
overflow-y: scroll;
|
||||
overscroll-behavior: contain;
|
||||
background: var(--MI_THEME-bg);
|
||||
scroll-padding-top: 60px; // TODO: ちゃんと計算する
|
||||
scroll-padding-bottom: 60px; // TODO: ちゃんと計算する
|
||||
}
|
||||
|
||||
.widgets {
|
||||
|
@ -10,6 +10,7 @@ import meta from '../../package.json';
|
||||
import packageInfo from './package.json' with { type: 'json' };
|
||||
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
|
||||
import pluginJson5 from './vite.json5.js';
|
||||
import pluginCreateSearchIndex from './lib/vite-plugin-create-search-index.js';
|
||||
|
||||
const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null;
|
||||
const host = url ? (new URL(url)).hostname : undefined;
|
||||
@ -34,7 +35,7 @@ const externalPackages = [
|
||||
},
|
||||
];
|
||||
|
||||
const hash = (str: string, seed = 0): number => {
|
||||
export const hash = (str: string, seed = 0): number => {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
@ -49,9 +50,9 @@ const hash = (str: string, seed = 0): number => {
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
};
|
||||
|
||||
const BASE62_DIGITS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
export const BASE62_DIGITS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
function toBase62(n: number): string {
|
||||
export function toBase62(n: number): string {
|
||||
if (n === 0) {
|
||||
return '0';
|
||||
}
|
||||
@ -83,6 +84,11 @@ export function getConfig(): UserConfig {
|
||||
},
|
||||
|
||||
plugins: [
|
||||
pluginCreateSearchIndex({
|
||||
targetFilePaths: ['src/pages/settings/*.vue'],
|
||||
exportFilePath: './src/scripts/autogen/settings-search-index.ts',
|
||||
verbose: process.env.FRONTEND_SEARCH_INDEX_VERBOSE === 'true',
|
||||
}),
|
||||
pluginVue(),
|
||||
pluginUnwindCssModuleClassName(),
|
||||
pluginJson5(),
|
||||
|
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@ -790,6 +790,9 @@ importers:
|
||||
json5:
|
||||
specifier: 2.2.3
|
||||
version: 2.2.3
|
||||
magic-string:
|
||||
specifier: 0.30.17
|
||||
version: 0.30.17
|
||||
matter-js:
|
||||
specifier: 0.20.0
|
||||
version: 0.20.0
|
||||
@ -7686,9 +7689,6 @@ packages:
|
||||
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
magic-string@0.30.11:
|
||||
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
|
||||
|
||||
magic-string@0.30.17:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
@ -13119,7 +13119,7 @@ snapshots:
|
||||
'@rollup/plugin-replace@6.0.2(rollup@4.34.9)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.34.9)
|
||||
magic-string: 0.30.11
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
rollup: 4.34.9
|
||||
|
||||
@ -14947,7 +14947,7 @@ snapshots:
|
||||
'@vue/compiler-ssr': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.11
|
||||
magic-string: 0.30.17
|
||||
postcss: 8.5.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@ -16734,7 +16734,7 @@ snapshots:
|
||||
|
||||
es-get-iterator@1.1.3:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
call-bind: 1.0.2
|
||||
get-intrinsic: 1.2.4
|
||||
has-symbols: 1.0.3
|
||||
is-arguments: 1.1.1
|
||||
@ -18968,10 +18968,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
magic-string@0.30.11:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
magic-string@0.30.17:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
@ -19773,7 +19769,7 @@ snapshots:
|
||||
|
||||
object-is@1.1.5:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.2.0
|
||||
|
||||
object-keys@1.1.1: {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user