Refine preferences (#15597)
* 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 41bffd3a13.
* 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
* wip
* wip
* wip
* Update rollup-plugin-unwind-css-module-class-name.test.ts
* Update navbar.vue
* clean up
* wip
* wip
* wip
* wip
* wip
* Update preferences-backups.vue
* Update common.ts
* Update preferences.ts
* wip
* wip
* wip
* wip
* Update MkPreferenceContainer.vue
* Update MkPreferenceContainer.vue
* Update MkPreferenceContainer.vue
* enhance: 検索で上下矢印を使用することで検索結果を移動できるように
* Update main-boot.ts
* refactor
* wip
* Update sounds.vue
* fix(frontend): PageWindowでSearchMarkerが動作するように
* enhance(frontend): SearchMarkerの点滅を一定時間で止める
* wip
* lint fix
* fix: 子要素監視が抜けていたのを修正
* アニメーションの回数はCSSで制御するように
* refactor
* enhance(frontend): 検索インデックス作成時のログを削減
* revert
* fix
* fix
* Update preferences.ts
* Update preferences.ts
* wip
* Update preferences.ts
* wip
* 🎨
* wip
* Update MkPreferenceContainer.vue
* wip
* Update preferences.ts
* wip
* Update preferences.ts
* Update preferences.ts
* wip
* wip
* Update preferences.ts
* wip
* wip
* Update preferences.ts
* Update CHANGELOG.md
* Update preferences.ts
* Update deck-store.ts
* deckStoreをdefaultStoreに統合
* wip
* defaultStore -> store
* Update profile.ts
* wip
* refactor
* wip: plugin
* plugin
* plugin
* plugin
* Update plugin.ts
* wip
* Update plugin.vue
* Update preferences.ts
* Update main-boot.ts
* wip
* fix test
* Update plugin.vue
* Update plugin.vue
* Update utility.ts
* wip
* wip
* Update utility.ts
* wip
* wip
* clean up
* Update utility.ts
---------
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:
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<MkLoading v-if="!loaded"/>
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
|
||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
|
||||
<div v-show="loaded" :class="$style.root">
|
||||
<img :src="serverErrorImageUrl" class="_ghost" :class="$style.img"/>
|
||||
<div class="_gaps">
|
||||
@@ -27,15 +27,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { version } from '@@/js/config.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import { version } from '@@/js/config.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { serverErrorImageUrl } from '@/instance.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
|
||||
@@ -143,7 +143,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
||||
import { physics } from '@/scripts/physics.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
|
||||
@@ -406,7 +406,7 @@ const easterEggEngine = ref<{ stop: () => void } | null>(null);
|
||||
const containerEl = shallowRef<HTMLElement>();
|
||||
|
||||
function iconLoaded() {
|
||||
const emojis = defaultStore.state.reactions;
|
||||
const emojis = store.state.reactions;
|
||||
const containerWidth = containerEl.value.offsetWidth;
|
||||
for (let i = 0; i < 32; i++) {
|
||||
easterEggEmojis.value.push({
|
||||
|
||||
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkInfo v-if="!defaultStore.reactiveState.abusesTutorial.value" closable @close="closeTutorial()">
|
||||
<MkInfo v-if="!store.reactiveState.abusesTutorial.value" closable @close="closeTutorial()">
|
||||
{{ i18n.ts._abuseUserReport.resolveTutorial }}
|
||||
</MkInfo>
|
||||
|
||||
@@ -68,7 +68,7 @@ import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
|
||||
const reports = shallowRef<InstanceType<typeof MkPagination>>();
|
||||
|
||||
@@ -93,7 +93,7 @@ function resolved(reportId) {
|
||||
}
|
||||
|
||||
function closeTutorial() {
|
||||
defaultStore.set('abusesTutorial', false);
|
||||
store.set('abusesTutorial', false);
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
@@ -78,6 +78,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { onMounted, ref, useCssModule } from 'vue';
|
||||
import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
|
||||
import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
||||
import type { DroppedFile } from '@/scripts/file-drop.js';
|
||||
import type { GridSetting } from '@/components/grid/grid.js';
|
||||
import type { GridRow } from '@/components/grid/row.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import {
|
||||
emptyStrToEmptyArray,
|
||||
@@ -88,7 +93,6 @@ import MkGrid from '@/components/grid/MkGrid.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
@@ -99,11 +103,7 @@ import { extractDroppedItems, flattenDroppedFiles } from '@/scripts/file-drop.js
|
||||
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
|
||||
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
||||
|
||||
import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
|
||||
import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
||||
import type { DroppedFile } from '@/scripts/file-drop.js';
|
||||
import type { GridSetting } from '@/components/grid/grid.js';
|
||||
import type { GridRow } from '@/components/grid/row.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const MAXIMUM_EMOJI_REGISTER_COUNT = 100;
|
||||
|
||||
@@ -244,8 +244,8 @@ function setupGrid(): GridSetting {
|
||||
|
||||
const uploadFolders = ref<FolderItem[]>([]);
|
||||
const gridItems = ref<GridItem[]>([]);
|
||||
const selectedFolderId = ref(defaultStore.state.uploadFolder);
|
||||
const keepOriginalUploading = ref(defaultStore.state.keepOriginalUploading);
|
||||
const selectedFolderId = ref(prefer.s.uploadFolder);
|
||||
const keepOriginalUploading = ref(prefer.s.keepOriginalUploading);
|
||||
const directoryToCategory = ref<boolean>(false);
|
||||
const registerButtonDisabled = ref<boolean>(false);
|
||||
const requestLogs = ref<RequestLogItem[]>([]);
|
||||
|
||||
@@ -17,7 +17,7 @@ import { onMounted, shallowRef, ref } from 'vue';
|
||||
import { Chart } from 'chart.js';
|
||||
import gradient from 'chartjs-plugin-gradient';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/scripts/chart-vline.js';
|
||||
import { initChart } from '@/scripts/init-chart.js';
|
||||
@@ -54,7 +54,7 @@ async function renderChart() {
|
||||
|
||||
const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
|
||||
|
||||
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
|
||||
const colorRead = '#3498db';
|
||||
const colorWrite = '#2ecc71';
|
||||
|
||||
@@ -27,7 +27,7 @@ import isChromatic from 'chromatic';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/scripts/chart-vline.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { alpha } from '@/scripts/color.js';
|
||||
import { initChart } from '@/scripts/init-chart.js';
|
||||
|
||||
@@ -68,7 +68,7 @@ onMounted(async () => {
|
||||
|
||||
const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
|
||||
|
||||
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const succColor = '#87e000';
|
||||
const failColor = '#ff4400';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else :class="$style.instances">
|
||||
<MkA v-for="(instance, i) in instances" :key="instance.id" v-tooltip.mfm.noDelay="`${instance.name}\n${instance.host}\n${instance.softwareName} ${instance.softwareVersion}`" :to="`/instance-info/${instance.host}`" :class="$style.instance">
|
||||
@@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const instances = ref<Misskey.entities.FederationInstance[]>([]);
|
||||
const fetching = ref(true);
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else :class="$style.root" class="_panel">
|
||||
<MkA v-for="user in moderators" :key="user.id" class="user" :to="`/admin/user/${user.id}`">
|
||||
@@ -18,9 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const moderators = ref<Misskey.entities.UserDetailed[] | null>(null);
|
||||
const fetching = ref(true);
|
||||
|
||||
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, shallowRef } from 'vue';
|
||||
import { Chart } from 'chart.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/scripts/chart-vline.js';
|
||||
import { alpha } from '@/scripts/color.js';
|
||||
@@ -67,7 +67,7 @@ const color =
|
||||
'?' as never;
|
||||
|
||||
onMounted(() => {
|
||||
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
|
||||
chartInstance = new Chart(chartEl.value, {
|
||||
type: 'line',
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else :class="$style.root">
|
||||
<div class="item _panel users">
|
||||
@@ -68,7 +68,7 @@ import MkNumberDiff from '@/components/MkNumberDiff.vue';
|
||||
import MkNumber from '@/components/MkNumber.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { customEmojis } from '@/custom-emojis.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const stats = ref<Misskey.entities.StatsResponse | null>(null);
|
||||
const usersComparedToThePrevDay = ref<number>();
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div :class="$style.root">
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else class="users">
|
||||
<MkA v-for="(user, i) in newUsers" :key="user.id" :to="`/admin/user/${user.id}`" class="user">
|
||||
@@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
|
||||
const fetching = ref(true);
|
||||
|
||||
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, shallowRef } from 'vue';
|
||||
import { Chart } from 'chart.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/scripts/chart-vline.js';
|
||||
import { alpha } from '@/scripts/color.js';
|
||||
@@ -67,7 +67,7 @@ const color =
|
||||
'?' as never;
|
||||
|
||||
onMounted(() => {
|
||||
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
|
||||
chartInstance = new Chart(chartEl.value, {
|
||||
type: 'line',
|
||||
|
||||
@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="800">
|
||||
<Transition
|
||||
:enterActiveClass="defaultStore.state.animation ? $style.fadeEnterActive : ''"
|
||||
:leaveActiveClass="defaultStore.state.animation ? $style.fadeLeaveActive : ''"
|
||||
:enterFromClass="defaultStore.state.animation ? $style.fadeEnterFrom : ''"
|
||||
:leaveToClass="defaultStore.state.animation ? $style.fadeLeaveTo : ''"
|
||||
:enterActiveClass="prefer.s.animation ? $style.fadeEnterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.fadeLeaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.fadeEnterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.fadeLeaveTo : ''"
|
||||
mode="out-in"
|
||||
>
|
||||
<div v-if="announcement" :key="announcement.id" class="_panel" :class="$style.announcement">
|
||||
@@ -56,7 +56,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { $i, updateAccountPartial } from '@/account.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = defineProps<{
|
||||
announcementId: string;
|
||||
|
||||
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
|
||||
|
||||
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
||||
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
||||
<MkPostForm v-if="$i && prefer.r.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
||||
|
||||
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
|
||||
</div>
|
||||
@@ -75,6 +75,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
|
||||
@@ -85,16 +87,14 @@ import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import { url } from '@@/js/config.js';
|
||||
import { favoritedChannelsCache } from '@/cache.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkNote from '@/components/MkNote.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||
import { isSupportShare } from '@/scripts/navigator.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { notesSearchAvailable } from '@/scripts/check-permissions.js';
|
||||
|
||||
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
|
||||
<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
|
||||
<img v-if="defaultStore.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
|
||||
<img v-if="store.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
|
||||
<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/>
|
||||
<canvas ref="canvasEl" :class="$style.canvas"/>
|
||||
<Transition
|
||||
@@ -195,6 +195,8 @@ import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch
|
||||
import * as Matter from 'matter-js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { DropAndFusionGame } from 'misskey-bubble-game';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { apiUrl } from '@@/js/config.js';
|
||||
import type { Mono } from 'misskey-bubble-game';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
@@ -203,15 +205,14 @@ import MkNumber from '@/components/MkNumber.vue';
|
||||
import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { apiUrl } from '@@/js/config.js';
|
||||
import { $i } from '@/account.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
type FrontendMonoDefinition = {
|
||||
id: string;
|
||||
@@ -586,8 +587,8 @@ const showConfig = ref(false);
|
||||
const replaying = ref(false);
|
||||
const replayPlaybackRate = ref(1);
|
||||
const currentFrame = ref(0);
|
||||
const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
|
||||
const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
|
||||
const bgmVolume = ref(prefer.s['game.dropAndFusion'].bgmVolume);
|
||||
const sfxVolume = ref(prefer.s['game.dropAndFusion'].sfxVolume);
|
||||
|
||||
watch(replayPlaybackRate, (newValue) => {
|
||||
game.replayPlaybackRate = newValue;
|
||||
@@ -623,7 +624,7 @@ function loadMonoTextures() {
|
||||
if (renderer.textures[mono.img]) return;
|
||||
|
||||
let src = mono.img;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
|
||||
if (monoTextureUrls[mono.img]) {
|
||||
src = monoTextureUrls[mono.img];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
@@ -649,7 +650,6 @@ function loadMonoTextures() {
|
||||
function getTextureImageUrl(mono: Mono) {
|
||||
const def = monoDefinitions.value.find(x => x.id === mono.id)!;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (monoTextureUrls[def.img]) {
|
||||
return monoTextureUrls[def.img];
|
||||
|
||||
@@ -853,13 +853,13 @@ function exportLog() {
|
||||
}
|
||||
|
||||
function updateSettings<
|
||||
K extends keyof typeof defaultStore.state.dropAndFusion,
|
||||
V extends typeof defaultStore.state.dropAndFusion[K],
|
||||
K extends keyof typeof prefer.s['game.dropAndFusion'],
|
||||
V extends typeof prefer.s['game.dropAndFusion'][K],
|
||||
>(key: K, value: V) {
|
||||
const changes: { [P in K]?: V } = {};
|
||||
changes[key] = value;
|
||||
defaultStore.set('dropAndFusion', {
|
||||
...defaultStore.state.dropAndFusion,
|
||||
prefer.set('game.dropAndFusion', {
|
||||
...prefer.s['game.dropAndFusion'],
|
||||
...changes,
|
||||
});
|
||||
}
|
||||
@@ -909,8 +909,8 @@ function getGameImageDriveFile() {
|
||||
formData.append('name', `bubble-game-${Date.now()}.png`);
|
||||
formData.append('isSensitive', 'false');
|
||||
formData.append('i', $i.token);
|
||||
if (defaultStore.state.uploadFolder) {
|
||||
formData.append('folderId', defaultStore.state.uploadFolder);
|
||||
if (prefer.s.uploadFolder) {
|
||||
formData.append('folderId', prefer.s.uploadFolder);
|
||||
}
|
||||
|
||||
window.fetch(apiUrl + '/drive/files/create', {
|
||||
|
||||
@@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="700">
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="flash" :key="flash.id">
|
||||
<Transition :name="defaultStore.state.animation ? 'zoom' : ''" mode="out-in">
|
||||
<Transition :name="prefer.s.animation ? 'zoom' : ''" mode="out-in">
|
||||
<div v-if="started" :class="$style.started">
|
||||
<div class="main _panel">
|
||||
<MkAsUi v-if="root" :component="root" :components="components"/>
|
||||
@@ -79,7 +79,7 @@ import { registerAsUiLib } from '@/scripts/aiscript/ui.js';
|
||||
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkCode from '@/components/MkCode.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { isSupportShare } from '@/scripts/navigator.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
|
||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="1000" :marginMin="16" :marginMax="32">
|
||||
<div class="_root">
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="post" class="rkxwuolj">
|
||||
<div class="files">
|
||||
<div v-for="file in post.files" :key="file.id" class="file">
|
||||
@@ -65,6 +65,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref, defineAsyncComponent } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
@@ -72,15 +74,13 @@ import MkContainer from '@/components/MkContainer.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
||||
import MkFollowButton from '@/components/MkFollowButton.vue';
|
||||
import { url } from '@@/js/config.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { isSupportShare } from '@/scripts/navigator.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -45,18 +45,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue';
|
||||
import type { Extension } from '@/components/MkExtensionInstaller.vue';
|
||||
import type { AiScriptPluginMeta } from '@/plugin.js';
|
||||
import MkLoading from '@/components/global/MkLoading.vue';
|
||||
import MkExtensionInstaller from '@/components/MkExtensionInstaller.vue';
|
||||
import type { Extension } from '@/components/MkExtensionInstaller.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkUrl from '@/components/global/MkUrl.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js';
|
||||
import type { AiScriptPluginMeta } from '@/scripts/install-plugin.js';
|
||||
import { parseThemeCode, installTheme } from '@/scripts/install-theme.js';
|
||||
import { parsePluginMeta, installPlugin } from '@/plugin.js';
|
||||
import { parseThemeCode, installTheme } from '@/scripts/theme.js';
|
||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
@@ -67,15 +67,15 @@ import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import { userListsCache } from '@/cache.js';
|
||||
import { signinRequired } from '@/account.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import { mainRouter } from '@/router/main.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
const {
|
||||
enableInfiniteScroll,
|
||||
} = defaultStore.reactiveState;
|
||||
} = prefer.r;
|
||||
|
||||
const props = defineProps<{
|
||||
listId: string;
|
||||
|
||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="800">
|
||||
<div>
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="note">
|
||||
<div v-if="showNext" class="_margin">
|
||||
<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
|
||||
@@ -61,7 +61,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { dateString } from '@/filters/date.js';
|
||||
import MkClipPreview from '@/components/MkClipPreview.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||
import { getAppearNote } from '@/scripts/get-appear-note.js';
|
||||
import { serverContext, assertServerContext } from '@/server-context.js';
|
||||
|
||||
@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="800">
|
||||
<Transition
|
||||
:enterActiveClass="defaultStore.state.animation ? $style.fadeEnterActive : ''"
|
||||
:leaveActiveClass="defaultStore.state.animation ? $style.fadeLeaveActive : ''"
|
||||
:enterFromClass="defaultStore.state.animation ? $style.fadeEnterFrom : ''"
|
||||
:leaveToClass="defaultStore.state.animation ? $style.fadeLeaveTo : ''"
|
||||
:enterActiveClass="prefer.s.animation ? $style.fadeEnterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.fadeLeaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.fadeEnterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.fadeLeaveTo : ''"
|
||||
mode="out-in"
|
||||
>
|
||||
<div v-if="page" :key="page.id" class="_gaps">
|
||||
@@ -100,11 +100,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref, defineAsyncComponent } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import XPage from '@/components/page/page.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import MkMediaImage from '@/components/MkMediaImage.vue';
|
||||
import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
|
||||
import MkFollowButton from '@/components/MkFollowButton.vue';
|
||||
@@ -113,7 +114,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkPagePreview from '@/components/MkPagePreview.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { pageViewInterruptors, defaultStore } from '@/store.js';
|
||||
import { pageViewInterruptors } from '@/store.js';
|
||||
import { deepClone } from '@/scripts/clone.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { isSupportShare } from '@/scripts/navigator.js';
|
||||
@@ -121,7 +122,7 @@ import { instance } from '@/instance.js';
|
||||
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -8,49 +8,63 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<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>
|
||||
<MkPreferenceContainer k="animation">
|
||||
<MkSwitch v-model="reduceAnimation">
|
||||
<template #label><SearchLabel>{{ i18n.ts.reduceUiAnimation }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif']">
|
||||
<MkSwitch v-model="disableShowingAnimatedImages">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableShowingAnimatedImages }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="disableShowingAnimatedImages">
|
||||
<MkSwitch v-model="disableShowingAnimatedImages">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableShowingAnimatedImages }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['mfm', 'enable', 'show', 'animated']">
|
||||
<MkSwitch v-model="animatedMfm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAnimatedMfm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="animatedMfm">
|
||||
<MkSwitch v-model="animatedMfm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAnimatedMfm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['swipe', 'horizontal', 'tab']">
|
||||
<MkSwitch v-model="enableHorizontalSwipe">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableHorizontalSwipe }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="enableHorizontalSwipe">
|
||||
<MkSwitch v-model="enableHorizontalSwipe">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableHorizontalSwipe }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['keep', 'screen', 'display', 'on']">
|
||||
<MkSwitch v-model="keepScreenOn">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepScreenOn }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="keepScreenOn">
|
||||
<MkSwitch v-model="keepScreenOn">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepScreenOn }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['native', 'system', 'video', 'audio', 'player', 'media']">
|
||||
<MkSwitch v-model="useNativeUIForVideoAudioPlayer">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="useNativeUiForVideoAudioPlayer">
|
||||
<MkSwitch v-model="useNativeUiForVideoAudioPlayer">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="contextMenu">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
@@ -60,18 +74,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
|
||||
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'));
|
||||
const reduceAnimation = prefer.model('animation', v => !v, v => !v);
|
||||
const animatedMfm = prefer.model('animatedMfm');
|
||||
const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages');
|
||||
const keepScreenOn = prefer.model('keepScreenOn');
|
||||
const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
|
||||
const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
|
||||
const contextMenu = prefer.model('contextMenu');
|
||||
|
||||
watch([
|
||||
keepScreenOn,
|
||||
|
||||
@@ -10,73 +10,85 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['blur']">
|
||||
<MkSwitch v-model="useBlurEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="useBlurEffect">
|
||||
<MkSwitch v-model="useBlurEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['blur', 'modal']">
|
||||
<MkSwitch v-model="useBlurEffectForModal">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="useBlurEffectForModal">
|
||||
<MkSwitch v-model="useBlurEffectForModal">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail']">
|
||||
<MkSwitch v-model="highlightSensitiveMedia">
|
||||
<template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="highlightSensitiveMedia">
|
||||
<MkSwitch v-model="highlightSensitiveMedia">
|
||||
<template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['avatar', 'icon', 'square']">
|
||||
<MkSwitch v-model="squareAvatars">
|
||||
<template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="squareAvatars">
|
||||
<MkSwitch v-model="squareAvatars">
|
||||
<template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']">
|
||||
<MkSwitch v-model="showAvatarDecorations">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="showAvatarDecorations">
|
||||
<MkSwitch v-model="showAvatarDecorations">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="showGapBetweenNotesInTimeline">
|
||||
<MkSwitch v-model="showGapBetweenNotesInTimeline">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['effect', 'show']">
|
||||
<MkSwitch v-model="enableSeasonalScreenEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="enableSeasonalScreenEffect">
|
||||
<MkSwitch v-model="enableSeasonalScreenEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="menuStyle">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="emojiStyle">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['font', 'size']">
|
||||
@@ -88,6 +100,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<option value="3"><span style="font-size: 17px;">Aa</span></option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['font', 'system', 'native']">
|
||||
<MkSwitch v-model="useSystemFont">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
@@ -97,46 +115,56 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<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>
|
||||
<MkPreferenceContainer k="reactionsDisplaySize">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['reaction', 'size', 'scale', 'display', 'width', 'limit']">
|
||||
<MkSwitch v-model="limitWidthOfReaction">
|
||||
<template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="limitWidthOfReaction">
|
||||
<MkSwitch v-model="limitWidthOfReaction">
|
||||
<template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="mediaListWithOneImageAppearance">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="instanceTicker">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="nsfw">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
@@ -148,21 +176,25 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<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>
|
||||
<MkPreferenceContainer k="notificationPosition">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="notificationStackAxis">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
|
||||
@@ -183,7 +215,7 @@ 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 { prefer } from '@/preferences.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
@@ -194,26 +226,27 @@ import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import { instance } from '@/instance.js';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
|
||||
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'));
|
||||
const showAvatarDecorations = prefer.model('showAvatarDecorations');
|
||||
const emojiStyle = prefer.model('emojiStyle');
|
||||
const menuStyle = prefer.model('menuStyle');
|
||||
const useBlurEffectForModal = prefer.model('useBlurEffectForModal');
|
||||
const useBlurEffect = prefer.model('useBlurEffect');
|
||||
const highlightSensitiveMedia = prefer.model('highlightSensitiveMedia');
|
||||
const squareAvatars = prefer.model('squareAvatars');
|
||||
const enableSeasonalScreenEffect = prefer.model('enableSeasonalScreenEffect');
|
||||
const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline');
|
||||
const mediaListWithOneImageAppearance = prefer.model('mediaListWithOneImageAppearance');
|
||||
const reactionsDisplaySize = prefer.model('reactionsDisplaySize');
|
||||
const limitWidthOfReaction = prefer.model('limitWidthOfReaction');
|
||||
const notificationPosition = prefer.model('notificationPosition');
|
||||
const notificationStackAxis = prefer.model('notificationStackAxis');
|
||||
const nsfw = prefer.model('nsfw');
|
||||
const instanceTicker = prefer.model('instanceTicker');
|
||||
|
||||
watch(fontSize, () => {
|
||||
if (fontSize.value == null) {
|
||||
|
||||
@@ -23,14 +23,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { computed } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const navWindow = computed(deckStore.makeGetterSetter('navWindow'));
|
||||
const useSimpleUiForNonRootPages = computed(deckStore.makeGetterSetter('useSimpleUiForNonRootPages'));
|
||||
const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn'));
|
||||
const columnAlign = computed(deckStore.makeGetterSetter('columnAlign'));
|
||||
const navWindow = prefer.model('deck.navWindow');
|
||||
const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages');
|
||||
const alwaysShowMainColumn = prefer.model('deck.alwaysShowMainColumn');
|
||||
const columnAlign = prefer.model('deck.columnAlign');
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
|
||||
@@ -50,17 +50,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</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>
|
||||
<MkPreferenceContainer k="keepOriginalUploading">
|
||||
<MkSwitch v-model="keepOriginalUploading">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepOriginalUploading }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.keepOriginalUploadingDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="keepOriginalFilename">
|
||||
<MkSwitch v-model="keepOriginalFilename">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepOriginalFilename }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file']">
|
||||
@@ -93,11 +97,12 @@ import FormSplit from '@/components/form/split.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import MkChart from '@/components/MkChart.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { signinRequired } from '@/account.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
@@ -120,8 +125,8 @@ const meterStyle = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading'));
|
||||
const keepOriginalFilename = computed(defaultStore.makeGetterSetter('keepOriginalFilename'));
|
||||
const keepOriginalUploading = prefer.model('keepOriginalUploading');
|
||||
const keepOriginalFilename = prefer.model('keepOriginalFilename');
|
||||
|
||||
misskeyApi('drive').then(info => {
|
||||
capacity.value = info.capacity;
|
||||
@@ -129,9 +134,9 @@ misskeyApi('drive').then(info => {
|
||||
fetching.value = false;
|
||||
});
|
||||
|
||||
if (defaultStore.state.uploadFolder) {
|
||||
if (prefer.s.uploadFolder) {
|
||||
misskeyApi('drive/folders/show', {
|
||||
folderId: defaultStore.state.uploadFolder,
|
||||
folderId: prefer.s.uploadFolder,
|
||||
}).then(response => {
|
||||
uploadFolder.value = response;
|
||||
});
|
||||
@@ -139,11 +144,11 @@ if (defaultStore.state.uploadFolder) {
|
||||
|
||||
function chooseUploadFolder() {
|
||||
os.selectDriveFolder(false).then(async folder => {
|
||||
defaultStore.set('uploadFolder', folder[0] ? folder[0].id : null);
|
||||
prefer.set('uploadFolder', folder[0] ? folder[0].id : null);
|
||||
os.success();
|
||||
if (defaultStore.state.uploadFolder) {
|
||||
if (prefer.s.uploadFolder) {
|
||||
uploadFolder.value = await misskeyApi('drive/folders/show', {
|
||||
folderId: defaultStore.state.uploadFolder,
|
||||
folderId: prefer.s.uploadFolder,
|
||||
});
|
||||
} else {
|
||||
uploadFolder.value = null;
|
||||
|
||||
@@ -89,37 +89,45 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #label>{{ i18n.ts.emojiPickerDisplay }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkRadios v-model="emojiPickerScale">
|
||||
<template #label>{{ i18n.ts.size }}</template>
|
||||
<option :value="1">{{ i18n.ts.small }}</option>
|
||||
<option :value="2">{{ i18n.ts.medium }}</option>
|
||||
<option :value="3">{{ i18n.ts.large }}</option>
|
||||
</MkRadios>
|
||||
<MkPreferenceContainer k="emojiPickerScale">
|
||||
<MkRadios v-model="emojiPickerScale">
|
||||
<template #label>{{ i18n.ts.size }}</template>
|
||||
<option :value="1">{{ i18n.ts.small }}</option>
|
||||
<option :value="2">{{ i18n.ts.medium }}</option>
|
||||
<option :value="3">{{ i18n.ts.large }}</option>
|
||||
</MkRadios>
|
||||
</MkPreferenceContainer>
|
||||
|
||||
<MkRadios v-model="emojiPickerWidth">
|
||||
<template #label>{{ i18n.ts.numberOfColumn }}</template>
|
||||
<option :value="1">5</option>
|
||||
<option :value="2">6</option>
|
||||
<option :value="3">7</option>
|
||||
<option :value="4">8</option>
|
||||
<option :value="5">9</option>
|
||||
</MkRadios>
|
||||
<MkPreferenceContainer k="emojiPickerWidth">
|
||||
<MkRadios v-model="emojiPickerWidth">
|
||||
<template #label>{{ i18n.ts.numberOfColumn }}</template>
|
||||
<option :value="1">5</option>
|
||||
<option :value="2">6</option>
|
||||
<option :value="3">7</option>
|
||||
<option :value="4">8</option>
|
||||
<option :value="5">9</option>
|
||||
</MkRadios>
|
||||
</MkPreferenceContainer>
|
||||
|
||||
<MkRadios v-model="emojiPickerHeight">
|
||||
<template #label>{{ i18n.ts.height }}</template>
|
||||
<option :value="1">{{ i18n.ts.small }}</option>
|
||||
<option :value="2">{{ i18n.ts.medium }}</option>
|
||||
<option :value="3">{{ i18n.ts.large }}</option>
|
||||
<option :value="4">{{ i18n.ts.large }}+</option>
|
||||
</MkRadios>
|
||||
<MkPreferenceContainer k="emojiPickerHeight">
|
||||
<MkRadios v-model="emojiPickerHeight">
|
||||
<template #label>{{ i18n.ts.height }}</template>
|
||||
<option :value="1">{{ i18n.ts.small }}</option>
|
||||
<option :value="2">{{ i18n.ts.medium }}</option>
|
||||
<option :value="3">{{ i18n.ts.large }}</option>
|
||||
<option :value="4">{{ i18n.ts.large }}+</option>
|
||||
</MkRadios>
|
||||
</MkPreferenceContainer>
|
||||
|
||||
<MkSelect v-model="emojiPickerStyle">
|
||||
<template #label>{{ i18n.ts.style }}</template>
|
||||
<template #caption>{{ i18n.ts.needReloadToApply }}</template>
|
||||
<option value="auto">{{ i18n.ts.auto }}</option>
|
||||
<option value="popup">{{ i18n.ts.popup }}</option>
|
||||
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
||||
</MkSelect>
|
||||
<MkPreferenceContainer k="emojiPickerStyle">
|
||||
<MkSelect v-model="emojiPickerStyle">
|
||||
<template #label>{{ i18n.ts.style }}</template>
|
||||
<template #caption>{{ i18n.ts.needReloadToApply }}</template>
|
||||
<option value="auto">{{ i18n.ts.auto }}</option>
|
||||
<option value="popup">{{ i18n.ts.popup }}</option>
|
||||
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
||||
</MkSelect>
|
||||
</MkPreferenceContainer>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
@@ -127,14 +135,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import Sortable from 'vuedraggable';
|
||||
import type { Ref } from 'vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { deepClone } from '@/scripts/clone.js';
|
||||
@@ -143,14 +151,16 @@ import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
|
||||
import MkEmoji from '@/components/global/MkEmoji.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
|
||||
const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(defaultStore.state.reactions));
|
||||
const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmojis));
|
||||
const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(store.state.reactions));
|
||||
const pinnedEmojis: Ref<string[]> = ref(deepClone(store.state.pinnedEmojis));
|
||||
|
||||
const emojiPickerScale = computed(defaultStore.makeGetterSetter('emojiPickerScale'));
|
||||
const emojiPickerWidth = computed(defaultStore.makeGetterSetter('emojiPickerWidth'));
|
||||
const emojiPickerHeight = computed(defaultStore.makeGetterSetter('emojiPickerHeight'));
|
||||
const emojiPickerStyle = computed(defaultStore.makeGetterSetter('emojiPickerStyle'));
|
||||
const emojiPickerScale = prefer.model('emojiPickerScale');
|
||||
const emojiPickerWidth = prefer.model('emojiPickerWidth');
|
||||
const emojiPickerHeight = prefer.model('emojiPickerHeight');
|
||||
const emojiPickerStyle = prefer.model('emojiPickerStyle');
|
||||
|
||||
const removeReaction = (reaction: string, ev: MouseEvent) => remove(pinnedEmojisForReaction, reaction, ev);
|
||||
const chooseReaction = (ev: MouseEvent) => pickEmoji(pinnedEmojisForReaction, ev);
|
||||
@@ -210,7 +220,7 @@ async function setDefault(itemsRef: Ref<string[]>) {
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
itemsRef.value = deepClone(defaultStore.def.reactions.default);
|
||||
itemsRef.value = deepClone(store.def.reactions.default);
|
||||
}
|
||||
|
||||
async function pickEmoji(itemsRef: Ref<string[]>, ev: MouseEvent) {
|
||||
@@ -230,13 +240,13 @@ function getHTMLElement(ev: MouseEvent): HTMLElement {
|
||||
}
|
||||
|
||||
watch(pinnedEmojisForReaction, () => {
|
||||
defaultStore.set('reactions', pinnedEmojisForReaction.value);
|
||||
store.set('reactions', pinnedEmojisForReaction.value);
|
||||
}, {
|
||||
deep: true,
|
||||
});
|
||||
|
||||
watch(pinnedEmojis, () => {
|
||||
defaultStore.set('pinnedEmojis', pinnedEmojis.value);
|
||||
store.set('pinnedEmojis', pinnedEmojis.value);
|
||||
}, {
|
||||
deep: true,
|
||||
});
|
||||
|
||||
@@ -155,11 +155,11 @@ import { selectFile } from '@/scripts/select-file.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
|
||||
const excludeMutingUsers = ref(false);
|
||||
const excludeInactiveUsers = ref(false);
|
||||
const withReplies = ref(defaultStore.state.defaultWithReplies);
|
||||
const withReplies = ref(store.state.defaultWithReplies);
|
||||
|
||||
const onExportSuccess = () => {
|
||||
os.alert({
|
||||
|
||||
@@ -12,6 +12,10 @@ 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>
|
||||
<MkInfo v-if="!store.reactiveState.enablePreferencesAutoCloudBackup.value && store.reactiveState.showPreferencesAutoCloudBackupSuggestion.value" class="info">
|
||||
<div>{{ i18n.ts._preferencesBackup.autoPreferencesBackupIsNotEnabledForThisDevice }}</div>
|
||||
<div><button class="_textButton" @click="enableAutoBackup">{{ i18n.ts.enable }}</button> | <button class="_textButton" @click="skipAutoBackup">{{ i18n.ts.skip }}</button></div>
|
||||
</MkInfo>
|
||||
<MkSuperMenu :def="menuDef" :grid="narrow" :searchIndex="SETTING_INDEX"></MkSuperMenu>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,6 +45,8 @@ import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata }
|
||||
import * as os from '@/os.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import { searchIndexes } from '@/scripts/autogen/settings-search-index.js';
|
||||
import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utility.js';
|
||||
import { store } from '@/store.js';
|
||||
|
||||
const SETTING_INDEX = searchIndexes; // TODO: lazy load
|
||||
|
||||
@@ -65,6 +71,10 @@ const ro = new ResizeObserver((entries, observer) => {
|
||||
narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
|
||||
});
|
||||
|
||||
function skipAutoBackup() {
|
||||
store.set('showPreferencesAutoCloudBackupSuggestion', false);
|
||||
}
|
||||
|
||||
const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||
items: [{
|
||||
icon: 'ti ti-user',
|
||||
@@ -168,10 +178,12 @@ const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||
}],
|
||||
}, {
|
||||
items: [{
|
||||
icon: 'ti ti-device-floppy',
|
||||
text: i18n.ts.preferencesBackups,
|
||||
to: '/settings/preferences-backups',
|
||||
active: currentPage.value?.route.name === 'preferences-backups',
|
||||
type: 'button',
|
||||
icon: 'ti ti-settings-2',
|
||||
text: i18n.ts.preferencesProfile,
|
||||
action: async (ev: MouseEvent) => {
|
||||
os.popupMenu(getPreferencesProfileMenu(), ev.currentTarget ?? ev.target);
|
||||
},
|
||||
}, {
|
||||
type: 'button',
|
||||
icon: 'ti ti-trash',
|
||||
|
||||
@@ -186,8 +186,8 @@ import { signinRequired } from '@/account.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
@@ -210,7 +210,7 @@ const expandedRenoteMuteItems = ref([]);
|
||||
const expandedMuteItems = ref([]);
|
||||
const expandedBlockItems = ref([]);
|
||||
|
||||
const showSoftWordMutedWord = computed(defaultStore.makeGetterSetter('showSoftWordMutedWord'));
|
||||
const showSoftWordMutedWord = prefer.model('showSoftWordMutedWord');
|
||||
|
||||
watch([
|
||||
showSoftWordMutedWord,
|
||||
|
||||
@@ -53,22 +53,24 @@ import FormSlot from '@/components/form/slot.vue';
|
||||
import MkContainer from '@/components/MkContainer.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { navbarItemDef } from '@/navbar.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { PREF_DEF } from '@/preferences/def.js';
|
||||
|
||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||
|
||||
const items = ref(defaultStore.state.menu.map(x => ({
|
||||
const items = ref(prefer.s.menu.map(x => ({
|
||||
id: Math.random().toString(),
|
||||
type: x,
|
||||
})));
|
||||
|
||||
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
||||
const menuDisplay = computed(store.makeGetterSetter('menuDisplay'));
|
||||
|
||||
async function addItem() {
|
||||
const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
|
||||
const menu = Object.keys(navbarItemDef).filter(k => !prefer.s.menu.includes(k));
|
||||
const { canceled, result: item } = await os.select({
|
||||
title: i18n.ts.addItem,
|
||||
items: [...menu.map(k => ({
|
||||
@@ -89,12 +91,12 @@ function removeItem(index: number) {
|
||||
}
|
||||
|
||||
async function save() {
|
||||
defaultStore.set('menu', items.value.map(x => x.type));
|
||||
prefer.set('menu', items.value.map(x => x.type));
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
}
|
||||
|
||||
function reset() {
|
||||
items.value = defaultStore.def.menu.default.map(x => ({
|
||||
items.value = PREF_DEF.menu.default.map(x => ({
|
||||
id: Math.random().toString(),
|
||||
type: x,
|
||||
}));
|
||||
|
||||
@@ -117,20 +117,21 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { signout, signinRequired } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
const reportError = computed(defaultStore.makeGetterSetter('reportError'));
|
||||
const enableCondensedLine = computed(defaultStore.makeGetterSetter('enableCondensedLine'));
|
||||
const skipNoteRender = computed(defaultStore.makeGetterSetter('skipNoteRender'));
|
||||
const devMode = computed(defaultStore.makeGetterSetter('devMode'));
|
||||
const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies'));
|
||||
const reportError = prefer.model('reportError');
|
||||
const enableCondensedLine = prefer.model('enableCondensedLine');
|
||||
const skipNoteRender = prefer.model('skipNoteRender');
|
||||
const devMode = prefer.model('devMode');
|
||||
const defaultWithReplies = computed(store.makeGetterSetter('defaultWithReplies'));
|
||||
|
||||
watch(skipNoteRender, async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
|
||||
@@ -23,10 +23,10 @@ import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { installPlugin } from '@/scripts/install-plugin.js';
|
||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { installPlugin } from '@/plugin.js';
|
||||
|
||||
const code = ref<string | null>(null);
|
||||
|
||||
|
||||
@@ -4,72 +4,92 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
|
||||
<SearchMarker path="/settings/plugin" :label="i18n.ts.plugins" :keywords="['plugin']" icon="ti ti-plug">
|
||||
<div class="_gaps_m">
|
||||
<FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.manage }}</template>
|
||||
<div class="_gaps_s">
|
||||
<div v-for="plugin in plugins" :key="plugin.id" class="_panel _gaps_m" style="padding: 20px;">
|
||||
<div class="_gaps_s">
|
||||
<span style="display: flex; align-items: center;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
|
||||
<MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch>
|
||||
</div>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.author }}</template>
|
||||
<template #value>{{ plugin.author }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.description }}</template>
|
||||
<template #value>{{ plugin.description }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.permission }}</template>
|
||||
<template #value>
|
||||
<ul style="margin-top: 0; margin-bottom: 0;">
|
||||
<li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
|
||||
<li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="ti ti-settings"></i> {{ i18n.ts.settings }}</MkButton>
|
||||
<MkButton inline danger @click="uninstall(plugin)"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-terminal-2"></i></template>
|
||||
<template #label>{{ i18n.ts._plugin.viewLog }}</template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.manage }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder v-for="plugin in plugins" :key="plugin.installId">
|
||||
<template #icon><i class="ti ti-plug"></i></template>
|
||||
<template #suffix>
|
||||
<i v-if="plugin.active" class="ti ti-player-play" style="color: var(--MI_THEME-accent);"></i>
|
||||
<i v-else class="ti ti-player-pause" style="opacity: 0.7;"></i>
|
||||
</template>
|
||||
<template #label>
|
||||
<div :style="plugin.active ? '' : 'opacity: 0.7;'">
|
||||
{{ plugin.name }}
|
||||
<span style="margin-left: 1em; opacity: 0.7;">v{{ plugin.version }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #caption>
|
||||
{{ plugin.description }}
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="copy(pluginLogs.get(plugin.id)?.join('\n'))"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
|
||||
<MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="ti ti-settings"></i> {{ i18n.ts.settings }}</MkButton>
|
||||
<MkButton inline danger @click="uninstall(plugin)"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div class="_gaps_s">
|
||||
<span style="display: flex; align-items: center;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
|
||||
<MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch>
|
||||
</div>
|
||||
|
||||
<MkCode :code="pluginLogs.get(plugin.id)?.join('\n') ?? ''"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-code"></i></template>
|
||||
<template #label>{{ i18n.ts._plugin.viewSource }}</template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="copy(plugin.src)"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
|
||||
<div class="_gaps_s">
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.author }}</template>
|
||||
<template #value>{{ plugin.author }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.description }}</template>
|
||||
<template #value>{{ plugin.description }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.permission }}</template>
|
||||
<template #value>
|
||||
<ul style="margin-top: 0; margin-bottom: 0;">
|
||||
<li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
|
||||
<li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
|
||||
<MkCode :code="plugin.src ?? ''" lang="is"/>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-terminal-2"></i></template>
|
||||
<template #label>{{ i18n.ts._plugin.viewLog }}</template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="copy(pluginLogs.get(plugin.installId)?.join('\n'))"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkCode :code="pluginLogs.get(plugin.installId)?.join('\n') ?? ''"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-code"></i></template>
|
||||
<template #label>{{ i18n.ts._plugin.viewSource }}</template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="copy(plugin.src)"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkCode :code="plugin.src ?? ''" lang="ais"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -83,19 +103,16 @@ import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { ColdDeviceStorage } from '@/store.js';
|
||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { pluginLogs } from '@/plugin.js';
|
||||
import { changePluginActive, configPlugin, pluginLogs, uninstallPlugin } from '@/plugin.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const plugins = ref(ColdDeviceStorage.get('plugins'));
|
||||
const plugins = prefer.r.plugins;
|
||||
|
||||
async function uninstall(plugin) {
|
||||
ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id));
|
||||
await os.apiWithDialog('i/revoke-token', {
|
||||
token: plugin.token,
|
||||
});
|
||||
await uninstallPlugin(plugin);
|
||||
nextTick(() => {
|
||||
unisonReload();
|
||||
});
|
||||
@@ -106,30 +123,15 @@ function copy(text) {
|
||||
os.success();
|
||||
}
|
||||
|
||||
// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする
|
||||
async function config(plugin) {
|
||||
const config = plugin.config;
|
||||
for (const key in plugin.configData) {
|
||||
config[key].default = plugin.configData[key];
|
||||
}
|
||||
|
||||
const { canceled, result } = await os.form(plugin.name, config);
|
||||
if (canceled) return;
|
||||
|
||||
const coldPlugins = ColdDeviceStorage.get('plugins');
|
||||
coldPlugins.find(p => p.id === plugin.id)!.configData = result;
|
||||
ColdDeviceStorage.set('plugins', coldPlugins);
|
||||
|
||||
await configPlugin(plugin);
|
||||
nextTick(() => {
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function changeActive(plugin, active) {
|
||||
const coldPlugins = ColdDeviceStorage.get('plugins');
|
||||
coldPlugins.find(p => p.id === plugin.id)!.active = active;
|
||||
ColdDeviceStorage.set('plugins', coldPlugins);
|
||||
|
||||
changePluginActive(plugin, active);
|
||||
nextTick(() => {
|
||||
location.reload();
|
||||
});
|
||||
|
||||
@@ -1,465 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<div :class="$style.buttons">
|
||||
<MkButton inline primary @click="saveNew">{{ i18n.ts._preferencesBackups.saveNew }}</MkButton>
|
||||
<MkButton inline @click="loadFile">{{ i18n.ts._preferencesBackups.loadFile }}</MkButton>
|
||||
</div>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts._preferencesBackups.list }}</template>
|
||||
<template v-if="profiles && Object.keys(profiles).length > 0">
|
||||
<div class="_gaps_s">
|
||||
<div
|
||||
v-for="(profile, id) in profiles"
|
||||
:key="id"
|
||||
class="_panel"
|
||||
:class="$style.profile"
|
||||
@click="$event => menu($event, id)"
|
||||
@contextmenu.prevent.stop="$event => menu($event, id)"
|
||||
>
|
||||
<div :class="$style.profileName">{{ profile.name }}</div>
|
||||
<div :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.createdAt({ date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div>
|
||||
<div v-if="profile.updatedAt" :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.updatedAt({ date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="profiles">
|
||||
<MkInfo>{{ i18n.ts._preferencesBackups.noBackups }}</MkInfo>
|
||||
</div>
|
||||
<MkLoading v-else/>
|
||||
</FormSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { version, host } from '@@/js/config.js';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { ColdDeviceStorage, defaultStore } from '@/store.js';
|
||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||
import { useStream } from '@/stream.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
|
||||
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
|
||||
'collapseRenotes',
|
||||
'menu',
|
||||
'visibility',
|
||||
'localOnly',
|
||||
'statusbars',
|
||||
'widgets',
|
||||
'tl',
|
||||
'pinnedUserLists',
|
||||
'overridedDeviceKind',
|
||||
'serverDisconnectedBehavior',
|
||||
'nsfw',
|
||||
'highlightSensitiveMedia',
|
||||
'animation',
|
||||
'animatedMfm',
|
||||
'advancedMfm',
|
||||
'showReactionsCount',
|
||||
'loadRawImages',
|
||||
'imageNewTab',
|
||||
'dataSaver',
|
||||
'disableShowingAnimatedImages',
|
||||
'emojiStyle',
|
||||
'menuStyle',
|
||||
'useBlurEffectForModal',
|
||||
'useBlurEffect',
|
||||
'showFixedPostForm',
|
||||
'showFixedPostFormInChannel',
|
||||
'enableInfiniteScroll',
|
||||
'useReactionPickerForContextMenu',
|
||||
'showGapBetweenNotesInTimeline',
|
||||
'instanceTicker',
|
||||
'emojiPickerScale',
|
||||
'emojiPickerWidth',
|
||||
'emojiPickerHeight',
|
||||
'emojiPickerStyle',
|
||||
'defaultSideView',
|
||||
'menuDisplay',
|
||||
'reportError',
|
||||
'squareAvatars',
|
||||
'showAvatarDecorations',
|
||||
'numberOfPageCache',
|
||||
'showNoteActionsOnlyHover',
|
||||
'showClipButtonInNoteFooter',
|
||||
'reactionsDisplaySize',
|
||||
'forceShowAds',
|
||||
'aiChanMode',
|
||||
'devMode',
|
||||
'mediaListWithOneImageAppearance',
|
||||
'notificationPosition',
|
||||
'notificationStackAxis',
|
||||
'keepScreenOn',
|
||||
'defaultWithReplies',
|
||||
'disableStreamingTimeline',
|
||||
'useGroupedNotifications',
|
||||
'sound_masterVolume',
|
||||
'sound_note',
|
||||
'sound_noteMy',
|
||||
'sound_notification',
|
||||
];
|
||||
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||
'lightTheme',
|
||||
'darkTheme',
|
||||
'syncDeviceDarkMode',
|
||||
'plugins',
|
||||
];
|
||||
|
||||
const scope = ['clientPreferencesProfiles'];
|
||||
|
||||
const profileProps = ['name', 'createdAt', 'updatedAt', 'misskeyVersion', 'settings', 'host'];
|
||||
|
||||
type Profile = {
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string | null;
|
||||
misskeyVersion: string;
|
||||
host: string;
|
||||
settings: {
|
||||
hot: Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
||||
cold: Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
||||
fontSize: string | null;
|
||||
useSystemFont: 't' | null;
|
||||
wallpaper: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
const connection = $i && useStream().useChannel('main');
|
||||
|
||||
const profiles = ref<Record<string, Profile> | null>(null);
|
||||
|
||||
misskeyApi('i/registry/get-all', { scope })
|
||||
.then(res => {
|
||||
profiles.value = res || {};
|
||||
});
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return value != null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function validate(profile: any): void {
|
||||
if (!isObject(profile)) throw new Error('not an object');
|
||||
|
||||
// Check if unnecessary properties exist
|
||||
if (Object.keys(profile).some(key => !profileProps.includes(key))) throw new Error('Unnecessary properties exist');
|
||||
|
||||
if (!profile.name) throw new Error('Missing required prop: name');
|
||||
if (!profile.misskeyVersion) throw new Error('Missing required prop: misskeyVersion');
|
||||
|
||||
// Check if createdAt and updatedAt is Date
|
||||
// https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date
|
||||
if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt as any).getTime())) throw new Error('createdAt is falsy or not Date');
|
||||
if (profile.updatedAt) {
|
||||
if (Number.isNaN(new Date(profile.updatedAt as any).getTime())) {
|
||||
throw new Error('updatedAt is not Date');
|
||||
}
|
||||
} else if (profile.updatedAt !== null) {
|
||||
throw new Error('updatedAt is not null');
|
||||
}
|
||||
|
||||
if (!profile.settings) throw new Error('Missing required prop: settings');
|
||||
if (!isObject(profile.settings)) throw new Error('Invalid prop: settings');
|
||||
}
|
||||
|
||||
function getSettings(): Profile['settings'] {
|
||||
const hot = {} as Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
||||
for (const key of defaultStoreSaveKeys) {
|
||||
hot[key] = defaultStore.state[key];
|
||||
}
|
||||
|
||||
const cold = {} as Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
||||
for (const key of coldDeviceStorageSaveKeys) {
|
||||
cold[key] = ColdDeviceStorage.get(key);
|
||||
}
|
||||
|
||||
return {
|
||||
hot,
|
||||
cold,
|
||||
fontSize: miLocalStorage.getItem('fontSize'),
|
||||
useSystemFont: miLocalStorage.getItem('useSystemFont') as 't' | null,
|
||||
wallpaper: miLocalStorage.getItem('wallpaper'),
|
||||
};
|
||||
}
|
||||
|
||||
async function saveNew(): Promise<void> {
|
||||
if (!profiles.value) return;
|
||||
|
||||
const { canceled, result: name } = await os.inputText({
|
||||
title: i18n.ts._preferencesBackups.inputName,
|
||||
default: '',
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
if (Object.values(profiles.value).some(x => x.name === name)) {
|
||||
return os.alert({
|
||||
title: i18n.ts._preferencesBackups.cannotSave,
|
||||
text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }),
|
||||
});
|
||||
}
|
||||
|
||||
const id = uuid();
|
||||
const profile: Profile = {
|
||||
name,
|
||||
createdAt: (new Date()).toISOString(),
|
||||
updatedAt: null,
|
||||
misskeyVersion: version,
|
||||
host,
|
||||
settings: getSettings(),
|
||||
};
|
||||
await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
|
||||
}
|
||||
|
||||
function loadFile(): void {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.multiple = false;
|
||||
input.onchange = async () => {
|
||||
if (!profiles.value) return;
|
||||
if (!input.files || input.files.length === 0) return;
|
||||
|
||||
const file = input.files[0];
|
||||
|
||||
if (file.type !== 'application/json') {
|
||||
return os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts._preferencesBackups.cannotLoad,
|
||||
text: i18n.ts._preferencesBackups.invalidFile,
|
||||
});
|
||||
}
|
||||
|
||||
let profile: Profile;
|
||||
try {
|
||||
profile = JSON.parse(await file.text()) as unknown as Profile;
|
||||
validate(profile);
|
||||
} catch (err) {
|
||||
return os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts._preferencesBackups.cannotLoad,
|
||||
text: (err as any)?.message ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
const id = uuid();
|
||||
await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
|
||||
|
||||
// 一応廃棄
|
||||
(window as any).__misskey_input_ref__ = null;
|
||||
};
|
||||
|
||||
// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
|
||||
// iOS Safari で正常に動かす為のおまじない
|
||||
(window as any).__misskey_input_ref__ = input;
|
||||
|
||||
input.click();
|
||||
}
|
||||
|
||||
async function applyProfile(id: string): Promise<void> {
|
||||
if (!profiles.value) return;
|
||||
|
||||
const profile = profiles.value[id];
|
||||
|
||||
const { canceled: cancel1 } = await os.confirm({
|
||||
type: 'warning',
|
||||
title: i18n.ts._preferencesBackups.apply,
|
||||
text: i18n.tsx._preferencesBackups.applyConfirm({ name: profile.name }),
|
||||
});
|
||||
if (cancel1) return;
|
||||
|
||||
// TODO: バージョン or ホストが違ったらさらに警告を表示
|
||||
|
||||
const settings = profile.settings;
|
||||
|
||||
// defaultStore
|
||||
for (const key of defaultStoreSaveKeys) {
|
||||
if (settings.hot[key] !== undefined) {
|
||||
defaultStore.set(key, settings.hot[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// coldDeviceStorage
|
||||
for (const key of coldDeviceStorageSaveKeys) {
|
||||
if (settings.cold[key] !== undefined) {
|
||||
ColdDeviceStorage.set(key, settings.cold[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// fontSize
|
||||
if (settings.fontSize) {
|
||||
miLocalStorage.setItem('fontSize', settings.fontSize);
|
||||
} else {
|
||||
miLocalStorage.removeItem('fontSize');
|
||||
}
|
||||
|
||||
// useSystemFont
|
||||
if (settings.useSystemFont) {
|
||||
miLocalStorage.setItem('useSystemFont', settings.useSystemFont);
|
||||
} else {
|
||||
miLocalStorage.removeItem('useSystemFont');
|
||||
}
|
||||
|
||||
// wallpaper
|
||||
if (settings.wallpaper != null) {
|
||||
miLocalStorage.setItem('wallpaper', settings.wallpaper);
|
||||
} else {
|
||||
miLocalStorage.removeItem('wallpaper');
|
||||
}
|
||||
|
||||
const { canceled: cancel2 } = await os.confirm({
|
||||
type: 'info',
|
||||
text: i18n.ts.reloadToApplySetting,
|
||||
});
|
||||
if (cancel2) return;
|
||||
|
||||
unisonReload();
|
||||
}
|
||||
|
||||
async function deleteProfile(id: string): Promise<void> {
|
||||
if (!profiles.value) return;
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
title: i18n.ts.delete,
|
||||
text: i18n.tsx.deleteAreYouSure({ x: profiles.value[id].name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
await os.apiWithDialog('i/registry/remove', { scope, key: id });
|
||||
delete profiles.value[id];
|
||||
}
|
||||
|
||||
async function save(id: string): Promise<void> {
|
||||
if (!profiles.value) return;
|
||||
|
||||
const { name, createdAt } = profiles.value[id];
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
title: i18n.ts._preferencesBackups.save,
|
||||
text: i18n.tsx._preferencesBackups.saveConfirm({ name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const profile: Profile = {
|
||||
name,
|
||||
createdAt,
|
||||
updatedAt: (new Date()).toISOString(),
|
||||
misskeyVersion: version,
|
||||
host,
|
||||
settings: getSettings(),
|
||||
};
|
||||
await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
|
||||
}
|
||||
|
||||
async function rename(id: string): Promise<void> {
|
||||
if (!profiles.value) return;
|
||||
|
||||
const { canceled: cancel1, result: name } = await os.inputText({
|
||||
title: i18n.ts._preferencesBackups.inputName,
|
||||
default: '',
|
||||
});
|
||||
if (cancel1 || profiles.value[id].name === name) return;
|
||||
|
||||
if (Object.values(profiles.value).some(x => x.name === name)) {
|
||||
return os.alert({
|
||||
title: i18n.ts._preferencesBackups.cannotSave,
|
||||
text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }),
|
||||
});
|
||||
}
|
||||
|
||||
const registry = Object.assign({}, { ...profiles.value[id] });
|
||||
|
||||
const { canceled: cancel2 } = await os.confirm({
|
||||
type: 'info',
|
||||
title: i18n.ts.rename,
|
||||
text: i18n.tsx._preferencesBackups.renameConfirm({ old: registry.name, new: name }),
|
||||
});
|
||||
if (cancel2) return;
|
||||
|
||||
registry.name = name;
|
||||
await os.apiWithDialog('i/registry/set', { scope, key: id, value: registry });
|
||||
}
|
||||
|
||||
function menu(ev: MouseEvent, profileId: string) {
|
||||
if (!profiles.value) return;
|
||||
|
||||
return os.popupMenu([{
|
||||
text: i18n.ts._preferencesBackups.apply,
|
||||
icon: 'ti ti-check',
|
||||
action: () => applyProfile(profileId),
|
||||
}, {
|
||||
type: 'a',
|
||||
text: i18n.ts.download,
|
||||
icon: 'ti ti-download',
|
||||
href: URL.createObjectURL(new Blob([JSON.stringify(profiles.value[profileId], null, 2)], { type: 'application/json' })),
|
||||
download: `${profiles.value[profileId].name}.json`,
|
||||
}, { type: 'divider' }, {
|
||||
text: i18n.ts.rename,
|
||||
icon: 'ti ti-forms',
|
||||
action: () => rename(profileId),
|
||||
}, {
|
||||
text: i18n.ts._preferencesBackups.save,
|
||||
icon: 'ti ti-device-floppy',
|
||||
action: () => save(profileId),
|
||||
}, { type: 'divider' }, {
|
||||
text: i18n.ts.delete,
|
||||
icon: 'ti ti-trash',
|
||||
action: () => deleteProfile(profileId),
|
||||
danger: true,
|
||||
}], (ev.currentTarget ?? ev.target ?? undefined) as unknown as HTMLElement | undefined);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// streamingのuser storage updateイベントを監視して更新
|
||||
connection?.on('registryUpdated', ({ scope: recievedScope, key, value }) => {
|
||||
if (!recievedScope || recievedScope.length !== scope.length || recievedScope[0] !== scope[0]) return;
|
||||
if (!profiles.value) return;
|
||||
|
||||
profiles.value[key] = value;
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
connection?.off('registryUpdated');
|
||||
});
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: i18n.ts.preferencesBackups,
|
||||
icon: 'ti ti-device-floppy',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: var(--MI-margin);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.profile {
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
|
||||
&Name {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&Time {
|
||||
font-size: .85em;
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -33,30 +33,36 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<FormSection>
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['post', 'form', 'timeline']">
|
||||
<MkSwitch v-model="showFixedPostForm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="showFixedPostForm">
|
||||
<MkSwitch v-model="showFixedPostForm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['post', 'form', 'timeline', 'channel']">
|
||||
<MkSwitch v-model="showFixedPostFormInChannel">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="showFixedPostFormInChannel">
|
||||
<MkSwitch v-model="showFixedPostFormInChannel">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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-if="prefer.r.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>
|
||||
<MkPreferenceContainer k="enableQuickAddMfmFunction">
|
||||
<MkSwitch v-model="enableQuickAddMfmFunction">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
@@ -68,40 +74,52 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<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>
|
||||
<MkPreferenceContainer k="collapseRenotes">
|
||||
<MkSwitch v-model="collapseRenotes">
|
||||
<template #label><SearchLabel>{{ i18n.ts.collapseRenotes }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.collapseRenotesDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['hover', 'show', 'footer', 'action']">
|
||||
<MkSwitch v-model="showNoteActionsOnlyHover">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showNoteActionsOnlyHover }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="showNoteActionsOnlyHover">
|
||||
<MkSwitch v-model="showNoteActionsOnlyHover">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showNoteActionsOnlyHover }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['footer', 'action', 'clip', 'show']">
|
||||
<MkSwitch v-model="showClipButtonInNoteFooter">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showClipButtonInNoteFooter }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="showClipButtonInNoteFooter">
|
||||
<MkSwitch v-model="showClipButtonInNoteFooter">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showClipButtonInNoteFooter }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced']">
|
||||
<MkSwitch v-model="advancedMfm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAdvancedMfm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="advancedMfm">
|
||||
<MkSwitch v-model="advancedMfm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAdvancedMfm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['reaction', 'count', 'show']">
|
||||
<MkSwitch v-model="showReactionsCount">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showReactionsCount }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="showReactionsCount">
|
||||
<MkSwitch v-model="showReactionsCount">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showReactionsCount }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment']">
|
||||
<MkSwitch v-model="loadRawImages">
|
||||
<template #label><SearchLabel>{{ i18n.ts.loadRawImages }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="loadRawImages">
|
||||
<MkSwitch v-model="loadRawImages">
|
||||
<template #label><SearchLabel>{{ i18n.ts.loadRawImages }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,9 +132,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['group']">
|
||||
<MkSwitch v-model="useGroupedNotifications">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="useGroupedNotifications">
|
||||
<MkSwitch v-model="useGroupedNotifications">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
@@ -129,62 +149,88 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<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>
|
||||
<MkPreferenceContainer k="imageNewTab">
|
||||
<MkSwitch v-model="imageNewTab">
|
||||
<template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['reaction', 'picker', 'contextmenu', 'open']">
|
||||
<MkSwitch v-model="useReactionPickerForContextMenu">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useReactionPickerForContextMenu }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="useReactionPickerForContextMenu">
|
||||
<MkSwitch v-model="useReactionPickerForContextMenu">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useReactionPickerForContextMenu }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['load', 'auto', 'more']">
|
||||
<MkSwitch v-model="enableInfiniteScroll">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableInfiniteScroll }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="enableInfiniteScroll">
|
||||
<MkSwitch v-model="enableInfiniteScroll">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableInfiniteScroll }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['disable', 'streaming', 'timeline']">
|
||||
<MkSwitch v-model="disableStreamingTimeline">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="disableStreamingTimeline">
|
||||
<MkSwitch v-model="disableStreamingTimeline">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['follow', 'confirm', 'always']">
|
||||
<MkSwitch v-model="alwaysConfirmFollow">
|
||||
<template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="alwaysConfirmFollow">
|
||||
<MkSwitch v-model="alwaysConfirmFollow">
|
||||
<template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm']">
|
||||
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">
|
||||
<template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="confirmWhenRevealingSensitiveMedia">
|
||||
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">
|
||||
<template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['reaction', 'confirm']">
|
||||
<MkSwitch v-model="confirmOnReact">
|
||||
<template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="confirmOnReact">
|
||||
<MkSwitch v-model="confirmOnReact">
|
||||
<template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['remember', 'keep', 'note', 'cw']">
|
||||
<MkPreferenceContainer k="keepCw">
|
||||
<MkSwitch v-model="keepCw">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepCw }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="serverDisconnectedBehavior">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="numberOfPageCache">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :label="i18n.ts.dataSaver" :keywords="['datasaver']">
|
||||
@@ -229,18 +275,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div class="_gaps">
|
||||
<SearchMarker :keywords="['ad', 'show']">
|
||||
<MkSwitch v-model="forceShowAds">
|
||||
<template #label><SearchLabel>{{ i18n.ts.forceShowAds }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="forceShowAds">
|
||||
<MkSwitch v-model="forceShowAds">
|
||||
<template #label><SearchLabel>{{ i18n.ts.forceShowAds }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkPreferenceContainer k="hemisphere">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['emoji', 'dictionary', 'additional', 'extra']">
|
||||
@@ -248,8 +298,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<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>
|
||||
<MkButton v-if="store.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) }}{{ store.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
@@ -272,7 +322,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<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';
|
||||
@@ -284,41 +333,44 @@ 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 { store } 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 { prefer } from '@/preferences.js';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
|
||||
const lang = ref(miLocalStorage.getItem('lang'));
|
||||
const dataSaver = ref(defaultStore.state.dataSaver);
|
||||
const dataSaver = ref(prefer.s.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'));
|
||||
const overridedDeviceKind = computed(store.makeGetterSetter('overridedDeviceKind'));
|
||||
|
||||
const keepCw = prefer.model('keepCw');
|
||||
const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior');
|
||||
const hemisphere = prefer.model('hemisphere');
|
||||
const showNoteActionsOnlyHover = prefer.model('showNoteActionsOnlyHover');
|
||||
const showClipButtonInNoteFooter = prefer.model('showClipButtonInNoteFooter');
|
||||
const collapseRenotes = prefer.model('collapseRenotes');
|
||||
const advancedMfm = prefer.model('advancedMfm');
|
||||
const showReactionsCount = prefer.model('showReactionsCount');
|
||||
const enableQuickAddMfmFunction = prefer.model('enableQuickAddMfmFunction');
|
||||
const forceShowAds = prefer.model('forceShowAds');
|
||||
const loadRawImages = prefer.model('loadRawImages');
|
||||
const imageNewTab = prefer.model('imageNewTab');
|
||||
const showFixedPostForm = prefer.model('showFixedPostForm');
|
||||
const showFixedPostFormInChannel = prefer.model('showFixedPostFormInChannel');
|
||||
const numberOfPageCache = prefer.model('numberOfPageCache');
|
||||
const enableInfiniteScroll = prefer.model('enableInfiniteScroll');
|
||||
const useReactionPickerForContextMenu = prefer.model('useReactionPickerForContextMenu');
|
||||
const disableStreamingTimeline = prefer.model('disableStreamingTimeline');
|
||||
const useGroupedNotifications = prefer.model('useGroupedNotifications');
|
||||
const alwaysConfirmFollow = prefer.model('alwaysConfirmFollow');
|
||||
const confirmWhenRevealingSensitiveMedia = prefer.model('confirmWhenRevealingSensitiveMedia');
|
||||
const confirmOnReact = prefer.model('confirmOnReact');
|
||||
const contextMenu = prefer.model('contextMenu');
|
||||
|
||||
watch(lang, () => {
|
||||
miLocalStorage.setItem('lang', lang.value as string);
|
||||
@@ -356,7 +408,7 @@ function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) {
|
||||
|
||||
function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
|
||||
async function main() {
|
||||
const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
|
||||
const currentIndexes = store.state.additionalUnicodeEmojiIndexes;
|
||||
|
||||
function download() {
|
||||
switch (lang) {
|
||||
@@ -368,7 +420,7 @@ function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
|
||||
}
|
||||
|
||||
currentIndexes[lang] = await download();
|
||||
await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
|
||||
await store.set('additionalUnicodeEmojiIndexes', currentIndexes);
|
||||
}
|
||||
|
||||
os.promiseDialog(main());
|
||||
@@ -376,9 +428,9 @@ function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
|
||||
|
||||
function removeEmojiIndex(lang: string) {
|
||||
async function main() {
|
||||
const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
|
||||
const currentIndexes = store.state.additionalUnicodeEmojiIndexes;
|
||||
delete currentIndexes[lang];
|
||||
await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
|
||||
await store.set('additionalUnicodeEmojiIndexes', currentIndexes);
|
||||
}
|
||||
|
||||
os.promiseDialog(main());
|
||||
@@ -393,16 +445,17 @@ async function setPinnedList() {
|
||||
})),
|
||||
});
|
||||
if (canceled) return;
|
||||
if (list == null) return;
|
||||
|
||||
defaultStore.set('pinnedUserLists', [list]);
|
||||
prefer.set('pinnedUserLists', [list]);
|
||||
}
|
||||
|
||||
function removePinnedList() {
|
||||
defaultStore.set('pinnedUserLists', []);
|
||||
prefer.set('pinnedUserLists', []);
|
||||
}
|
||||
|
||||
function enableAllDataSaver() {
|
||||
const g = { ...defaultStore.state.dataSaver };
|
||||
const g = { ...prefer.s.dataSaver };
|
||||
|
||||
Object.keys(g).forEach((key) => { g[key] = true; });
|
||||
|
||||
@@ -410,7 +463,7 @@ function enableAllDataSaver() {
|
||||
}
|
||||
|
||||
function disableAllDataSaver() {
|
||||
const g = { ...defaultStore.state.dataSaver };
|
||||
const g = { ...prefer.s.dataSaver };
|
||||
|
||||
Object.keys(g).forEach((key) => { g[key] = false; });
|
||||
|
||||
@@ -418,7 +471,7 @@ function disableAllDataSaver() {
|
||||
}
|
||||
|
||||
watch(dataSaver, (to) => {
|
||||
defaultStore.set('dataSaver', to);
|
||||
prefer.set('dataSaver', to);
|
||||
}, {
|
||||
deep: true,
|
||||
});
|
||||
|
||||
@@ -172,38 +172,41 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<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>
|
||||
<MkPreferenceContainer k="rememberNoteVisibility">
|
||||
<MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</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>
|
||||
<MkDisableSection :disabled="rememberNoteVisibility">
|
||||
<MkFolder>
|
||||
<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>
|
||||
<div class="_gaps_m">
|
||||
<MkPreferenceContainer k="defaultNoteVisibility">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
|
||||
<MkPreferenceContainer k="defaultNoteLocalOnly">
|
||||
<MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</MkDisableSection>
|
||||
</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>
|
||||
@@ -215,7 +218,6 @@ import MkSelect from '@/components/MkSelect.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { signinRequired } from '@/account.js';
|
||||
@@ -225,6 +227,8 @@ 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';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
@@ -241,10 +245,9 @@ const publicReactions = ref($i.publicReactions);
|
||||
const followingVisibility = ref($i.followingVisibility);
|
||||
const followersVisibility = ref($i.followersVisibility);
|
||||
|
||||
const defaultNoteVisibility = computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
|
||||
const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
|
||||
const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
|
||||
const keepCw = computed(defaultStore.makeGetterSetter('keepCw'));
|
||||
const defaultNoteVisibility = prefer.model('defaultNoteVisibility');
|
||||
const defaultNoteLocalOnly = prefer.model('defaultNoteLocalOnly');
|
||||
const rememberNoteVisibility = prefer.model('rememberNoteVisibility');
|
||||
|
||||
const makeNotesFollowersOnlyBefore_type = computed(() => {
|
||||
if (makeNotesFollowersOnlyBefore.value == null) {
|
||||
|
||||
@@ -168,7 +168,7 @@ import { signinRequired } from '@/account.js';
|
||||
import { langmap } from '@/scripts/langmap.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { globalEvents } from '@/events.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
@@ -177,7 +177,7 @@ const $i = signinRequired();
|
||||
|
||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||
|
||||
const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
|
||||
const reactionAcceptance = computed(store.makeGetterSetter('reactionAcceptance'));
|
||||
|
||||
function assertVaildLang(lang: string | null): lang is keyof typeof langmap {
|
||||
return lang != null && lang in langmap;
|
||||
|
||||
@@ -7,21 +7,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<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>
|
||||
<MkPreferenceContainer k="sound.notUseSound">
|
||||
<MkSwitch v-model="notUseSound">
|
||||
<template #label><SearchLabel>{{ i18n.ts.notUseSound }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['active', 'mute']">
|
||||
<MkSwitch v-model="useSoundOnlyWhenActive">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useSoundOnlyWhenActive }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
<MkPreferenceContainer k="sound.useSoundOnlyWhenActive">
|
||||
<MkSwitch v-model="useSoundOnlyWhenActive">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useSoundOnlyWhenActive }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<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>
|
||||
<MkPreferenceContainer k="sound.masterVolume">
|
||||
<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>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<FormSection>
|
||||
@@ -52,7 +58,8 @@ import { computed, 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 type { SoundStore } from '@/preferences/def.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
@@ -60,18 +67,19 @@ import MkFolder from '@/components/MkFolder.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { operationTypes } from '@/scripts/sound.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
import { PREF_DEF } from '@/preferences/def.js';
|
||||
|
||||
const notUseSound = computed(defaultStore.makeGetterSetter('sound_notUseSound'));
|
||||
const useSoundOnlyWhenActive = computed(defaultStore.makeGetterSetter('sound_useSoundOnlyWhenActive'));
|
||||
const masterVolume = computed(defaultStore.makeGetterSetter('sound_masterVolume'));
|
||||
const notUseSound = prefer.model('sound.notUseSound');
|
||||
const useSoundOnlyWhenActive = prefer.model('sound.useSoundOnlyWhenActive');
|
||||
const masterVolume = prefer.model('sound.masterVolume');
|
||||
|
||||
const sounds = ref<Record<OperationType, Ref<SoundStore>>>({
|
||||
note: defaultStore.reactiveState.sound_note,
|
||||
noteMy: defaultStore.reactiveState.sound_noteMy,
|
||||
notification: defaultStore.reactiveState.sound_notification,
|
||||
reaction: defaultStore.reactiveState.sound_reaction,
|
||||
note: prefer.r['sound.on.note'],
|
||||
noteMy: prefer.r['sound.on.noteMy'],
|
||||
notification: prefer.r['sound.on.notification'],
|
||||
reaction: prefer.r['sound.on.reaction'],
|
||||
});
|
||||
|
||||
function getSoundTypeName(f: SoundType): string {
|
||||
@@ -93,14 +101,14 @@ async function updated(type: keyof typeof sounds.value, sound) {
|
||||
volume: sound.volume,
|
||||
};
|
||||
|
||||
defaultStore.set(`sound_${type}`, v);
|
||||
prefer.set(`sound.on.${type}`, v);
|
||||
sounds.value[type] = v;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
for (const sound of Object.keys(sounds.value) as Array<keyof typeof sounds.value>) {
|
||||
const v = defaultStore.def[`sound_${sound}`].default;
|
||||
defaultStore.set(`sound_${sound}`, v);
|
||||
const v = PREF_DEF[`sound.on.${sound}`].default;
|
||||
prefer.set(`sound.on.${sound}`, v);
|
||||
sounds.value[sound] = v;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,17 +94,17 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { deepClone } from '@/scripts/clone.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = defineProps<{
|
||||
_id: string;
|
||||
userLists: Misskey.entities.UserList[] | null;
|
||||
}>();
|
||||
|
||||
const statusbar = reactive(deepClone(defaultStore.state.statusbars.find(x => x.id === props._id)));
|
||||
const statusbar = reactive(deepClone(prefer.s.statusbars.find(x => x.id === props._id)));
|
||||
|
||||
watch(() => statusbar.type, () => {
|
||||
if (statusbar.type === 'rss') {
|
||||
@@ -134,13 +134,13 @@ watch(() => statusbar.type, () => {
|
||||
watch(statusbar, save);
|
||||
|
||||
async function save() {
|
||||
const i = defaultStore.state.statusbars.findIndex(x => x.id === props._id);
|
||||
const statusbars = deepClone(defaultStore.state.statusbars);
|
||||
const i = prefer.s.statusbars.findIndex(x => x.id === props._id);
|
||||
const statusbars = deepClone(prefer.s.statusbars);
|
||||
statusbars[i] = deepClone(statusbar);
|
||||
defaultStore.set('statusbars', statusbars);
|
||||
prefer.set('statusbars', statusbars);
|
||||
}
|
||||
|
||||
function del() {
|
||||
defaultStore.set('statusbars', defaultStore.state.statusbars.filter(x => x.id !== props._id));
|
||||
prefer.set('statusbars', prefer.s.statusbars.filter(x => x.id !== props._id));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -22,11 +22,11 @@ import XStatusbar from './statusbar.statusbar.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const statusbars = defaultStore.reactiveState.statusbars;
|
||||
const statusbars = prefer.r.statusbars;
|
||||
|
||||
const userLists = ref<Misskey.entities.UserList[] | null>(null);
|
||||
|
||||
@@ -37,13 +37,13 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
async function add() {
|
||||
defaultStore.push('statusbars', {
|
||||
prefer.set('statusbars', [...statusbars.value, {
|
||||
id: uuid(),
|
||||
type: null,
|
||||
black: false,
|
||||
size: 'medium',
|
||||
props: {},
|
||||
});
|
||||
}]);
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { ref, computed } from 'vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js';
|
||||
import { parseThemeCode, previewTheme, installTheme } from '@/scripts/theme.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
@@ -75,6 +75,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, onActivated, ref, watch } from 'vue';
|
||||
import JSON5 from 'json5';
|
||||
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
|
||||
import type { MkSelectItem } from '@/components/MkSelect.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
@@ -84,15 +86,15 @@ import MkButton from '@/components/MkButton.vue';
|
||||
import { getBuiltinThemesRef } from '@/scripts/theme.js';
|
||||
import { selectFile } from '@/scripts/select-file.js';
|
||||
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
|
||||
import { ColdDeviceStorage, defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { uniqueBy } from '@/scripts/array.js';
|
||||
import { fetchThemes, getThemes } from '@/theme-store.js';
|
||||
import { getThemes } from '@/theme-store.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||
import * as os from '@/os.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const installedThemes = ref(getThemes());
|
||||
const builtinThemes = getBuiltinThemesRef();
|
||||
@@ -169,39 +171,39 @@ const darkThemeSelectorItems = computed(() => {
|
||||
return items;
|
||||
});
|
||||
|
||||
const darkTheme = ColdDeviceStorage.ref('darkTheme');
|
||||
const darkTheme = prefer.r.darkTheme;
|
||||
const darkThemeId = computed({
|
||||
get() {
|
||||
return darkTheme.value.id;
|
||||
return darkTheme.value ? darkTheme.value.id : defaultDarkTheme.id;
|
||||
},
|
||||
set(id) {
|
||||
const t = themes.value.find(x => x.id === id);
|
||||
if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる
|
||||
ColdDeviceStorage.set('darkTheme', t);
|
||||
prefer.set('darkTheme', t);
|
||||
}
|
||||
},
|
||||
});
|
||||
const lightTheme = ColdDeviceStorage.ref('lightTheme');
|
||||
const lightTheme = prefer.r.lightTheme;
|
||||
const lightThemeId = computed({
|
||||
get() {
|
||||
return lightTheme.value.id;
|
||||
return lightTheme.value ? lightTheme.value.id : defaultLightTheme.id;
|
||||
},
|
||||
set(id) {
|
||||
const t = themes.value.find(x => x.id === id);
|
||||
if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる
|
||||
ColdDeviceStorage.set('lightTheme', t);
|
||||
prefer.set('lightTheme', t);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
|
||||
const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
|
||||
const darkMode = computed(store.makeGetterSetter('darkMode'));
|
||||
const syncDeviceDarkMode = prefer.model('syncDeviceDarkMode');
|
||||
const wallpaper = ref(miLocalStorage.getItem('wallpaper'));
|
||||
const themesCount = installedThemes.value.length;
|
||||
|
||||
watch(syncDeviceDarkMode, () => {
|
||||
if (syncDeviceDarkMode.value) {
|
||||
defaultStore.set('darkMode', isDeviceDarkmode());
|
||||
store.set('darkMode', isDeviceDarkmode());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -215,12 +217,6 @@ watch(wallpaper, async () => {
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
fetchThemes().then(() => {
|
||||
installedThemes.value = getThemes();
|
||||
});
|
||||
});
|
||||
|
||||
fetchThemes().then(() => {
|
||||
installedThemes.value = getThemes();
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import { genEmbedCode } from '@/scripts/get-embed-code.js';
|
||||
|
||||
@@ -44,11 +44,11 @@ const pagination = {
|
||||
const notes = ref<InstanceType<typeof MkNotes>>();
|
||||
|
||||
async function post() {
|
||||
defaultStore.set('postFormHashtags', props.tag);
|
||||
defaultStore.set('postFormWithHashtags', true);
|
||||
store.set('postFormHashtags', props.tag);
|
||||
store.set('postFormWithHashtags', true);
|
||||
await os.post();
|
||||
defaultStore.set('postFormHashtags', '');
|
||||
defaultStore.set('postFormWithHashtags', false);
|
||||
store.set('postFormHashtags', '');
|
||||
store.set('postFormWithHashtags', false);
|
||||
notes.value?.pagingComponent?.reload();
|
||||
}
|
||||
|
||||
|
||||
@@ -78,24 +78,23 @@ import { toUnicode } from 'punycode.js';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import JSON5 from 'json5';
|
||||
|
||||
import lightTheme from '@@/themes/_light.json5';
|
||||
import darkTheme from '@@/themes/_dark.json5';
|
||||
import { host } from '@@/js/config.js';
|
||||
import type { Theme } from '@/scripts/theme.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
import { $i } from '@/account.js';
|
||||
import { applyTheme } from '@/scripts/theme.js';
|
||||
import type { Theme } from '@/scripts/theme.js';
|
||||
import { host } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
import { ColdDeviceStorage, defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { addTheme } from '@/theme-store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { useLeaveGuard } from '@/scripts/use-leave-guard.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const bgColors = [
|
||||
{ color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' },
|
||||
@@ -201,10 +200,10 @@ async function saveAs() {
|
||||
if (description.value) theme.value.desc = description.value;
|
||||
await addTheme(theme.value);
|
||||
applyTheme(theme.value);
|
||||
if (defaultStore.state.darkMode) {
|
||||
ColdDeviceStorage.set('darkTheme', theme.value);
|
||||
if (store.state.darkMode) {
|
||||
prefer.set('darkTheme', theme.value);
|
||||
} else {
|
||||
ColdDeviceStorage.set('lightTheme', theme.value);
|
||||
prefer.set('lightTheme', theme.value);
|
||||
}
|
||||
changed.value = false;
|
||||
os.alert({
|
||||
|
||||
@@ -9,10 +9,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkSpacer :contentMax="800">
|
||||
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
|
||||
<div :key="src" ref="rootEl">
|
||||
<MkInfo v-if="isBasicTimeline(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
||||
<MkInfo v-if="isBasicTimeline(src) && !store.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
||||
{{ i18n.ts._timelineDescription[src] }}
|
||||
</MkInfo>
|
||||
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/>
|
||||
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/>
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div :class="$style.tl">
|
||||
<MkTimeline
|
||||
@@ -36,25 +36,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue';
|
||||
import { scroll } from '@@/js/scroll.js';
|
||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import type { BasicTimelineType } from '@/timelines.js';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||
import { scroll } from '@@/js/scroll.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js';
|
||||
import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import { deepMerge } from '@/scripts/merge.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
||||
import type { BasicTimelineType } from '@/timelines.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
provide('shouldOmitHeaderTitle', true);
|
||||
|
||||
@@ -66,18 +67,18 @@ type TimelinePageSrc = BasicTimelineType | `list:${string}`;
|
||||
const queue = ref(0);
|
||||
const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global');
|
||||
const src = computed<TimelinePageSrc>({
|
||||
get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value),
|
||||
get: () => ($i ? store.reactiveState.tl.value.src : srcWhenNotSignin.value),
|
||||
set: (x) => saveSrc(x),
|
||||
});
|
||||
const withRenotes = computed<boolean>({
|
||||
get: () => defaultStore.reactiveState.tl.value.filter.withRenotes,
|
||||
get: () => store.reactiveState.tl.value.filter.withRenotes,
|
||||
set: (x) => saveTlFilter('withRenotes', x),
|
||||
});
|
||||
|
||||
// computed内での無限ループを防ぐためのフラグ
|
||||
const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>(
|
||||
defaultStore.reactiveState.tl.value.filter.withReplies ? 'withReplies' :
|
||||
defaultStore.reactiveState.tl.value.filter.onlyFiles ? 'onlyFiles' :
|
||||
store.reactiveState.tl.value.filter.withReplies ? 'withReplies' :
|
||||
store.reactiveState.tl.value.filter.onlyFiles ? 'onlyFiles' :
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -87,7 +88,7 @@ const withReplies = computed<boolean>({
|
||||
if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'onlyFiles') {
|
||||
return false;
|
||||
} else {
|
||||
return defaultStore.reactiveState.tl.value.filter.withReplies;
|
||||
return store.reactiveState.tl.value.filter.withReplies;
|
||||
}
|
||||
},
|
||||
set: (x) => saveTlFilter('withReplies', x),
|
||||
@@ -97,7 +98,7 @@ const onlyFiles = computed<boolean>({
|
||||
if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'withReplies') {
|
||||
return false;
|
||||
} else {
|
||||
return defaultStore.reactiveState.tl.value.filter.onlyFiles;
|
||||
return store.reactiveState.tl.value.filter.onlyFiles;
|
||||
}
|
||||
},
|
||||
set: (x) => saveTlFilter('onlyFiles', x),
|
||||
@@ -114,7 +115,7 @@ watch([withReplies, onlyFiles], ([withRepliesTo, onlyFilesTo]) => {
|
||||
});
|
||||
|
||||
const withSensitive = computed<boolean>({
|
||||
get: () => defaultStore.reactiveState.tl.value.filter.withSensitive,
|
||||
get: () => store.reactiveState.tl.value.filter.withSensitive,
|
||||
set: (x) => saveTlFilter('withSensitive', x),
|
||||
});
|
||||
|
||||
@@ -195,23 +196,23 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
|
||||
}
|
||||
|
||||
function saveSrc(newSrc: TimelinePageSrc): void {
|
||||
const out = deepMerge({ src: newSrc }, defaultStore.state.tl);
|
||||
const out = deepMerge({ src: newSrc }, store.state.tl);
|
||||
|
||||
if (newSrc.startsWith('userList:')) {
|
||||
const id = newSrc.substring('userList:'.length);
|
||||
out.userList = defaultStore.reactiveState.pinnedUserLists.value.find(l => l.id === id) ?? null;
|
||||
out.userList = prefer.r.pinnedUserLists.value.find(l => l.id === id) ?? null;
|
||||
}
|
||||
|
||||
defaultStore.set('tl', out);
|
||||
store.set('tl', out);
|
||||
if (['local', 'global'].includes(newSrc)) {
|
||||
srcWhenNotSignin.value = newSrc as 'local' | 'global';
|
||||
}
|
||||
}
|
||||
|
||||
function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) {
|
||||
function saveTlFilter(key: keyof typeof store.state.tl.filter, newValue: boolean) {
|
||||
if (key !== 'withReplies' || $i) {
|
||||
const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl);
|
||||
defaultStore.set('tl', out);
|
||||
const out = deepMerge({ filter: { [key]: newValue } }, store.state.tl);
|
||||
store.set('tl', out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,9 +231,9 @@ function focus(): void {
|
||||
|
||||
function closeTutorial(): void {
|
||||
if (!isBasicTimeline(src.value)) return;
|
||||
const before = defaultStore.state.timelineTutorials;
|
||||
const before = store.state.timelineTutorials;
|
||||
before[src.value] = true;
|
||||
defaultStore.set('timelineTutorials', before);
|
||||
store.set('timelineTutorials', before);
|
||||
}
|
||||
|
||||
function switchTlIfNeeded() {
|
||||
@@ -298,7 +299,7 @@ const headerActions = computed(() => {
|
||||
return tmp;
|
||||
});
|
||||
|
||||
const headerTabs = computed(() => [...(defaultStore.reactiveState.pinnedUserLists.value.map(l => ({
|
||||
const headerTabs = computed(() => [...(prefer.r.pinnedUserLists.value.map(l => ({
|
||||
key: 'list:' + l.id,
|
||||
title: l.name,
|
||||
icon: 'ti ti-star',
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { ChartDataset } from 'chart.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import gradient from 'chartjs-plugin-gradient';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/scripts/chart-vline.js';
|
||||
import { initChart } from '@/scripts/init-chart.js';
|
||||
@@ -64,7 +64,7 @@ async function renderChart() {
|
||||
|
||||
const raw = await misskeyApi('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' });
|
||||
|
||||
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
|
||||
const colorFollowLocal = '#008FFB';
|
||||
const colorFollowRemote = '#008FFB88';
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { ChartDataset } from 'chart.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import gradient from 'chartjs-plugin-gradient';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/scripts/chart-vline.js';
|
||||
import { initChart } from '@/scripts/init-chart.js';
|
||||
@@ -64,7 +64,7 @@ async function renderChart() {
|
||||
|
||||
const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
|
||||
|
||||
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
|
||||
const colorNormal = '#008FFB';
|
||||
const colorReply = '#FEB019';
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { ChartDataset } from 'chart.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import gradient from 'chartjs-plugin-gradient';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/scripts/chart-vline.js';
|
||||
import { initChart } from '@/scripts/init-chart.js';
|
||||
@@ -64,7 +64,7 @@ async function renderChart() {
|
||||
|
||||
const raw = await misskeyApi('charts/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' });
|
||||
|
||||
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
|
||||
const colorUser = '#3498db';
|
||||
const colorVisitor = '#2ecc71';
|
||||
|
||||
@@ -176,7 +176,6 @@ import number from '@/filters/number.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { $i, iAmModerator } from '@/account.js';
|
||||
import { dateString } from '@/filters/date.js';
|
||||
import { confetti } from '@/scripts/confetti.js';
|
||||
@@ -185,6 +184,7 @@ import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFf
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
|
||||
import MkSparkle from '@/components/MkSparkle.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
function calcAge(birthdate: string): number {
|
||||
const date = new Date(birthdate);
|
||||
@@ -236,7 +236,7 @@ watch(moderationNote, async () => {
|
||||
|
||||
const style = computed(() => {
|
||||
if (props.user.bannerUrl == null) return {};
|
||||
if (defaultStore.state.disableShowingAnimatedImages) {
|
||||
if (prefer.s.disableShowingAnimatedImages) {
|
||||
return {
|
||||
backgroundImage: `url(${ getStaticImageUrl(props.user.bannerUrl) })`,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user