1
0

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 41bffd3a13f55618bf939dc1c9acb2a77ead4054.

* inlining

* wip

* Update theme.vue

* 🎨

* wip normalize

* Update theme.vue

* キャッシュのパス変換

* build

* wip

* wip

* Update SearchMarker.vue

* i18n.ts['key'] の形式が取り出せない問題のFix

* build

* 仮でpath入れ

* 必ず絶対パスが使われるように

* wip

* 🎨

* storybookビルド時はcreateSearchIndexをしない

* inliningの構造化

* format code

* Update index.vue

* wip

* wip

* 🎨

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* clean up

* 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:
syuilo
2025-03-09 12:34:08 +09:00
committed by GitHub
parent 05cdc095c0
commit d30ddd4c2e
181 changed files with 3437 additions and 2463 deletions
@@ -57,12 +57,12 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['mute'],
},
{
id: 'xy5OOBB4A',
id: 'oALW4ja7U',
label: i18n.ts.useSoundOnlyWhenActive,
keywords: ['active', 'mute'],
},
{
id: '9MxYVIf7k',
id: 'BbJK2SKT2',
label: i18n.ts.masterVolume,
keywords: ['volume', 'master'],
},
@@ -267,15 +267,10 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['remember', 'keep', 'note', 'visibility'],
},
{
id: 'rhKwScbVS',
id: '1u5HZuujV',
label: i18n.ts.defaultNoteVisibility,
keywords: ['default', 'note', 'visibility'],
},
{
id: '3EmXVyevo',
label: i18n.ts.keepCw,
keywords: ['remember', 'keep', 'note', 'cw'],
},
],
label: i18n.ts.privacy,
keywords: ['privacy'],
@@ -301,50 +296,50 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['post', 'form', 'timeline'],
},
{
id: '9ra14w32V',
id: 'snyCQ5oKE',
label: i18n.ts.showFixedPostFormInChannel,
keywords: ['post', 'form', 'timeline', 'channel'],
},
{
id: '84MdeDWL1',
id: '8j36S4Ev6',
label: i18n.ts.pinnedList,
keywords: ['pinned', 'list'],
},
{
id: 'fYdWhBbrN',
id: 'CWpyT9vLK',
label: i18n.ts.enableQuickAddMfmFunction,
keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'],
},
{
id: '4huRldNp5',
id: 'puIqj1a8b',
children: [
{
id: 'puIqj1a8b',
id: '1x3JNXj8N',
label: i18n.ts.collapseRenotes,
keywords: ['renote', i18n.ts.collapseRenotesDescription],
},
{
id: 'wqpOC22Zm',
id: 'c98gbF9c6',
label: i18n.ts.showNoteActionsOnlyHover,
keywords: ['hover', 'show', 'footer', 'action'],
},
{
id: 'cjfAtxMzP',
id: '4LxdiOMNh',
label: i18n.ts.showClipButtonInNoteFooter,
keywords: ['footer', 'action', 'clip', 'show'],
},
{
id: 'khzxoCjtp',
id: '9gTCaLkIf',
label: i18n.ts.enableAdvancedMfm,
keywords: ['mfm', 'enable', 'show', 'advanced'],
},
{
id: 'uJkoVjTmF',
id: '6kMj4HVOg',
label: i18n.ts.showReactionsCount,
keywords: ['reaction', 'count', 'show'],
},
{
id: '9gTCaLkIf',
id: 'dPersnkzh',
label: i18n.ts.loadRawImages,
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'],
},
@@ -353,10 +348,10 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['note'],
},
{
id: '5G6O6qdis',
id: '5XhS6ukl8',
children: [
{
id: 'sYTvqUbhP',
id: '3GcWIaZf8',
label: i18n.ts.useGroupedNotifications,
keywords: ['group'],
},
@@ -365,55 +360,60 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['notification'],
},
{
id: 'c3xhLyXZ5',
id: 'dSGDnj2PA',
children: [
{
id: 'FbhoeuRAD',
id: '1LHOhDKGW',
label: i18n.ts.openImageInNewTab,
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'],
},
{
id: 'qixh85g2N',
id: 'DSzwvTp7i',
label: i18n.ts.useReactionPickerForContextMenu,
keywords: ['reaction', 'picker', 'contextmenu', 'open'],
},
{
id: 'd2H4E5ys6',
id: '5QTUzrpT3',
label: i18n.ts.enableInfiniteScroll,
keywords: ['load', 'auto', 'more'],
},
{
id: 'jC7LtTnmc',
id: '7Uf8ksn3q',
label: i18n.ts.disableStreamingTimeline,
keywords: ['disable', 'streaming', 'timeline'],
},
{
id: '8xazEqlgZ',
id: 'whKYKvaQB',
label: i18n.ts.alwaysConfirmFollow,
keywords: ['follow', 'confirm', 'always'],
},
{
id: 'wZqrDQZar',
id: 'nf4kcPeYw',
label: i18n.ts.confirmWhenRevealingSensitiveMedia,
keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'],
},
{
id: '5QTUzrpT3',
id: 'rRisK1YYQ',
label: i18n.ts.confirmOnReact,
keywords: ['reaction', 'confirm'],
},
{
id: 'nygexkaUk',
id: '6AH0lnjf1',
label: i18n.ts.keepCw,
keywords: ['remember', 'keep', 'note', 'cw'],
},
{
id: 'uHcTVSGDv',
label: i18n.ts.whenServerDisconnected,
keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'],
},
{
id: 'whKYKvaQB',
id: 'fzPca1Gk9',
label: i18n.ts.numberOfPageCache,
keywords: ['cache', 'page'],
},
{
id: 'lBbtAg0Hm',
id: 'mNU5IBln7',
label: i18n.ts.dataSaver,
keywords: ['datasaver'],
},
@@ -422,20 +422,20 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['behavior'],
},
{
id: 'y2v7CV9zs',
id: 'C3psHYdZn',
children: [
{
id: 'k1qTdyfzM',
id: 'iCEiAg4Wg',
label: i18n.ts.forceShowAds,
keywords: ['ad', 'show'],
},
{
id: 'e9As4Us48',
id: 'qj9eChQ5B',
label: i18n.ts.hemisphere,
keywords: [],
},
{
id: 'zvM13vl26',
id: 'uItIge5hw',
label: i18n.ts.additionalEmojiDictionary,
keywords: ['emoji', 'dictionary', 'additional', 'extra'],
},
@@ -449,6 +449,13 @@ export const searchIndexes: SearchIndexItem[] = [
path: '/settings/preferences',
icon: 'ti ti-adjustments',
},
{
id: 'mwkwtw83Y',
label: i18n.ts.plugins,
keywords: ['plugin'],
path: '/settings/plugin',
icon: 'ti ti-plug',
},
{
id: 'F1uK9ssiY',
children: [
@@ -626,17 +633,17 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['keep', 'original', 'raw', 'upload', i18n.ts.keepOriginalUploadingDescription],
},
{
id: 'oqUiI5w0s',
id: 'D8HUTGWE1',
label: i18n.ts.keepOriginalFilename,
keywords: ['keep', 'original', 'filename', i18n.ts.keepOriginalFilenameDescription],
},
{
id: 'Aszkikq9n',
id: '6xAvsWSZi',
label: i18n.ts.alwaysMarkSensitive,
keywords: ['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file'],
},
{
id: 'iGlVjsfVj',
id: 'csNNPF1KX',
label: i18n.ts.enableAutoSensitive,
keywords: ['auto', 'nsfw', 'sensitive', 'media', 'file', i18n.ts.enableAutoSensitiveDescription],
},
@@ -662,80 +669,80 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['blur'],
},
{
id: 'vbZvyLDC1',
id: 'C05WQNSIJ',
label: i18n.ts.useBlurEffectForModal,
keywords: ['blur', 'modal'],
},
{
id: '6fLNMTwNt',
id: 'snVKNr7Bw',
label: i18n.ts.highlightSensitiveMedia,
keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'],
},
{
id: 'hhvF8Z4pF',
id: 'DsS2CwjYE',
label: i18n.ts.squareAvatars,
keywords: ['avatar', 'icon', 'square'],
},
{
id: 'DsS2CwjYE',
id: 'xCcTDl651',
label: i18n.ts.showAvatarDecorations,
keywords: ['avatar', 'icon', 'decoration', 'show'],
},
{
id: 'pWZ0ypy2g',
id: '3dHw723VD',
label: i18n.ts.showGapBetweenNotesInTimeline,
keywords: ['note', 'timeline', 'gap'],
},
{
id: 'AfRMcC6IM',
label: i18n.ts.useSystemFont,
keywords: ['font', 'system', 'native'],
},
{
id: 'jD0qbxlzN',
id: 'AWi72xbrl',
label: i18n.ts.seasonalScreenEffect,
keywords: ['effect', 'show'],
},
{
id: 'EdYo3hOK',
id: 'Ces8FsJws',
label: i18n.ts.menuStyle,
keywords: ['menu', 'style', 'popup', 'drawer'],
},
{
id: '9mSlX0EkD',
id: 'wDr9xSXCv',
label: i18n.ts.emojiStyle,
keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'],
},
{
id: '44UmMwmUe',
id: 'vFB0pLzck',
label: i18n.ts.fontSize,
keywords: ['font', 'size'],
},
{
id: 'vFB0pLzck',
id: '23BhvYXPC',
label: i18n.ts.useSystemFont,
keywords: ['font', 'system', 'native'],
},
{
id: 'EeNLndAOa',
children: [
{
id: 'pc7IpPEU4',
id: 'rAAPoaodS',
label: i18n.ts.reactionsDisplaySize,
keywords: ['reaction', 'size', 'scale', 'display'],
},
{
id: 'siOW5aSwp',
id: 'qTLAvNWsc',
label: i18n.ts.limitWidthOfReaction,
keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'],
},
{
id: 'dDUvhk13F',
id: '2lWgzAm13',
label: i18n.ts.mediaListWithOneImageAppearance,
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'],
},
{
id: 'CLxNL1Rp0',
id: 'EU7HbxOR5',
label: i18n.ts.instanceTicker,
keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'],
},
{
id: 'dP2KWDYzD',
id: 'AEtM0FAp1',
label: i18n.ts.displayOfSensitiveMedia,
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'],
},
@@ -744,15 +751,15 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['note', 'display'],
},
{
id: 'dVOzi22IW',
id: 'A1FMC2Zon',
children: [
{
id: 'aoF4ufUwn',
id: 'CB37G5ZDo',
label: i18n.ts.position,
keywords: ['position'],
},
{
id: 'sKK2XSS69',
id: 'gGS2i19hS',
label: i18n.ts.stackAxis,
keywords: ['stack', 'axis', 'direction'],
},
@@ -775,32 +782,32 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['animation', 'motion', 'reduce'],
},
{
id: 'RhYwm8At',
id: 'cXr3tFdpa',
label: i18n.ts.disableShowingAnimatedImages,
keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'],
},
{
id: '5mZxz2cru',
id: 'Ok1UBwtP',
label: i18n.ts.enableAnimatedMfm,
keywords: ['mfm', 'enable', 'show', 'animated'],
},
{
id: 'bgjamYEis',
id: 'yPEpJigqY',
label: i18n.ts.enableHorizontalSwipe,
keywords: ['swipe', 'horizontal', 'tab'],
},
{
id: 'yPEpJigqY',
id: 'h7iZtdTU3',
label: i18n.ts.keepScreenOn,
keywords: ['keep', 'screen', 'display', 'on'],
},
{
id: 'oxwiGKMu0',
id: 'gP1BY3PDy',
label: i18n.ts.useNativeUIForVideoAudioPlayer,
keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
},
{
id: 'n90tffyiU',
id: 'jnMK3M6rs',
label: i18n.ts._contextMenu.title,
keywords: ['contextmenu', 'system', 'native'],
},
@@ -10,18 +10,20 @@ import { bundledThemesInfo } from 'shiki/themes';
import { bundledLanguagesInfo } from 'shiki/langs';
import lightTheme from '@@/themes/_light.json5';
import darkTheme from '@@/themes/_dark.json5';
import defaultLightTheme from '@@/themes/l-light.json5';
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
import { unique } from './array.js';
import { deepClone } from './clone.js';
import { deepMerge } from './merge.js';
import type { HighlighterCore, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki/core';
import { ColdDeviceStorage } from '@/store.js';
import { prefer } from '@/preferences.js';
let _highlighter: HighlighterCore | null = null;
export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>;
export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>;
export async function getTheme(mode: 'light' | 'dark', getName = false): Promise<ThemeRegistration | ThemeRegistrationRaw | string | null> {
const theme = deepClone(ColdDeviceStorage.get(mode === 'light' ? 'lightTheme' : 'darkTheme'));
const theme = deepClone(mode === 'light' ? prefer.s.lightTheme ?? defaultLightTheme : prefer.s.darkTheme ?? defaultDarkTheme);
if (theme.base) {
const base = [lightTheme, darkTheme].find(x => x.id === theme.base);
@@ -77,19 +79,19 @@ async function initHighlighter() {
],
});
ColdDeviceStorage.watch('lightTheme', async () => {
const newTheme = await getTheme('light');
if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
highlighter.loadTheme(newTheme);
}
});
ColdDeviceStorage.watch('darkTheme', async () => {
const newTheme = await getTheme('dark');
if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
highlighter.loadTheme(newTheme);
}
});
// TODO
//watch('lightTheme', async () => {
// const newTheme = await getTheme('light');
// if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
// highlighter.loadTheme(newTheme);
// }
//});
//watch('darkTheme', async () => {
// const newTheme = await getTheme('dark');
// if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
// highlighter.loadTheme(newTheme);
// }
//});
_highlighter = highlighter;
@@ -6,7 +6,7 @@
import { defineAsyncComponent, ref } from 'vue';
import type { Ref } from 'vue';
import { popup } from '@/os.js';
import { defaultStore } from '@/store.js';
import { store } from '@/store.js';
/**
* 絵文字ピッカーを表示する。
@@ -25,7 +25,7 @@ class EmojiPicker {
}
public async init() {
const emojisRef = defaultStore.reactiveState.pinnedEmojis;
const emojisRef = store.reactiveState.pinnedEmojis;
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src: this.src,
pinnedEmojis: emojisRef,
@@ -5,12 +5,12 @@
import * as Misskey from 'misskey-js';
import { defineAsyncComponent } from 'vue';
import type { MenuItem } from '@/types/menu.js';
import { i18n } from '@/i18n.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import type { MenuItem } from '@/types/menu.js';
import { defaultStore } from '@/store.js';
import { prefer } from '@/preferences.js';
function rename(file: Misskey.entities.DriveFile) {
os.inputText({
@@ -148,7 +148,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
action: () => deleteFile(file),
});
if (defaultStore.state.devMode) {
if (prefer.s.devMode) {
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
@@ -15,7 +15,7 @@ import { instance } from '@/instance.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { defaultStore, noteActions } from '@/store.js';
import { store, noteActions } from '@/store.js';
import { miLocalStorage } from '@/local-storage.js';
import { getUserMenu } from '@/scripts/get-user-menu.js';
import { clipsCache, favoritedChannelsCache } from '@/cache.js';
@@ -23,6 +23,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { isSupportShare } from '@/scripts/navigator.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
import { genEmbedCode } from '@/scripts/get-embed-code.js';
import { prefer } from '@/preferences.js';
export async function getNoteClipMenu(props: {
note: Misskey.entities.Note;
@@ -507,7 +508,7 @@ export function getNoteMenu(props: {
})));
}
if (defaultStore.state.devMode) {
if (prefer.s.devMode) {
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyNoteId,
@@ -558,7 +559,7 @@ export function getRenoteMenu(props: {
icon: 'ti ti-repeat',
action: () => {
const el = props.renoteButton.value;
if (el && defaultStore.state.animation) {
if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -596,7 +597,7 @@ export function getRenoteMenu(props: {
icon: 'ti ti-repeat',
action: () => {
const el = props.renoteButton.value;
if (el && defaultStore.state.animation) {
if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -605,8 +606,8 @@ export function getRenoteMenu(props: {
});
}
const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
const configuredVisibility = prefer.s.rememberNoteVisibility ? store.state.visibility : prefer.s.defaultNoteVisibility;
const localOnly = prefer.s.rememberNoteVisibility ? store.state.localOnly : prefer.s.defaultNoteLocalOnly;
let visibility = appearNote.visibility;
visibility = smallerVisibility(visibility, configuredVisibility);
@@ -647,7 +648,7 @@ export function getRenoteMenu(props: {
text: channel.name,
action: () => {
const el = props.renoteButton.value;
if (el && defaultStore.state.animation) {
if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -6,19 +6,20 @@
import { toUnicode } from 'punycode.js';
import { defineAsyncComponent, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { host, url } from '@@/js/config.js';
import type { IRouter } from '@/nirax.js';
import type { MenuItem } from '@/types/menu.js';
import { i18n } from '@/i18n.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { host, url } from '@@/js/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore, userActions } from '@/store.js';
import { userActions } from '@/store.js';
import { $i, iAmModerator } from '@/account.js';
import { notesSearchAvailable, canSearchNonLocalNotes } from '@/scripts/check-permissions.js';
import type { IRouter } from '@/nirax.js';
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
import { mainRouter } from '@/router/main.js';
import { genEmbedCode } from '@/scripts/get-embed-code.js';
import type { MenuItem } from '@/types/menu.js';
import { prefer } from '@/preferences.js';
export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) {
const meId = $i ? $i.id : null;
@@ -251,7 +252,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
listId: list.id,
userId: user.id,
}).then(() => {
list.userIds?.splice(list.userIds?.indexOf(user.id), 1);
list.userIds?.splice(list.userIds.indexOf(user.id), 1);
});
}
}));
@@ -398,7 +399,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
});
}
if (defaultStore.state.devMode) {
if (prefer.s.devMode) {
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyUserId,
+2 -2
View File
@@ -24,7 +24,7 @@ import {
import gradient from 'chartjs-plugin-gradient';
import zoomPlugin from 'chartjs-plugin-zoom';
import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
import { defaultStore } from '@/store.js';
import { store } from '@/store.js';
import 'chartjs-adapter-date-fns';
export function initChart() {
@@ -52,7 +52,7 @@ export function initChart() {
// フォントカラー
Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg');
Chart.defaults.borderColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
Chart.defaults.borderColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
Chart.defaults.animation = false;
}
@@ -1,131 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineAsyncComponent } from 'vue';
import { compareVersions } from 'compare-versions';
import { v4 as uuid } from 'uuid';
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
import type { Plugin } from '@/store.js';
import { ColdDeviceStorage } from '@/store.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
export type AiScriptPluginMeta = {
name: string;
version: string;
author: string;
description?: string;
permissions?: string[];
config?: Record<string, any>;
};
const parser = new Parser();
export function savePlugin({ id, meta, src, token }: {
id: string;
meta: AiScriptPluginMeta;
src: string;
token: string;
}) {
ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({
...meta,
id,
active: true,
configData: {},
token: token,
src: src,
} as Plugin));
}
export function isSupportedAiScriptVersion(version: string): boolean {
try {
return (compareVersions(version, '0.12.0') >= 0);
} catch (err) {
return false;
}
}
export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta> {
if (!code) {
throw new Error('code is required');
}
const lv = utils.getLangVersion(code);
if (lv == null) {
throw new Error('No language version annotation found');
} else if (!isSupportedAiScriptVersion(lv)) {
throw new Error(`Aiscript version '${lv}' is not supported`);
}
let ast;
try {
ast = parser.parse(code);
} catch (err) {
throw new Error('Aiscript syntax error');
}
const meta = Interpreter.collectMetadata(ast);
if (meta == null) {
throw new Error('Meta block not found');
}
const metadata = meta.get(null);
if (metadata == null) {
throw new Error('Metadata not found');
}
const { name, version, author, description, permissions, config } = metadata;
if (name == null || version == null || author == null) {
throw new Error('Required property not found');
}
return {
name,
version,
author,
description,
permissions,
config,
};
}
export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
if (!code) return;
let realMeta: AiScriptPluginMeta;
if (!meta) {
realMeta = await parsePluginMeta(code);
} else {
realMeta = meta;
}
const token = realMeta.permissions == null || realMeta.permissions.length === 0 ? null : await new Promise((res, rej) => {
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {
title: i18n.ts.tokenRequested,
information: i18n.ts.pluginTokenRequestedDescription,
initialName: realMeta.name,
initialPermissions: realMeta.permissions,
}, {
done: async result => {
const { name, permissions } = result;
const { token } = await misskeyApi('miauth/gen-token', {
session: null,
name: name,
permission: permissions,
});
res(token);
},
closed: () => dispose(),
});
});
savePlugin({
id: uuid(),
meta: realMeta,
token,
src: code,
});
}
@@ -1,38 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import JSON5 from 'json5';
import { addTheme, getThemes } from '@/theme-store.js';
import { applyTheme, validateTheme } from '@/scripts/theme.js';
import type { Theme } from '@/scripts/theme.js';
export function parseThemeCode(code: string): Theme {
let theme;
try {
theme = JSON5.parse(code);
} catch (err) {
throw new Error('Failed to parse theme json');
}
if (!validateTheme(theme)) {
throw new Error('This theme is invaild');
}
if (getThemes().some(t => t.id === theme.id)) {
throw new Error('This theme is already installed');
}
return theme;
}
export function previewTheme(code: string): void {
const theme = parseThemeCode(code);
if (theme) applyTheme(theme, false);
}
export async function installTheme(code: string): Promise<void> {
const theme = parseThemeCode(code);
if (!theme) return;
await addTheme(theme);
}
@@ -7,7 +7,7 @@ import * as Misskey from 'misskey-js';
import { defineAsyncComponent, ref } from 'vue';
import type { Ref } from 'vue';
import { popup } from '@/os.js';
import { defaultStore } from '@/store.js';
import { store } from '@/store.js';
class ReactionPicker {
private src: Ref<HTMLElement | null> = ref(null);
@@ -21,7 +21,7 @@ class ReactionPicker {
}
public async init() {
const reactionsRef = defaultStore.reactiveState.reactions;
const reactionsRef = store.reactiveState.reactions;
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src: this.src,
pinnedEmojis: reactionsRef,
+5 -5
View File
@@ -9,8 +9,8 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js';
import { uploadFile } from '@/scripts/upload.js';
import { prefer } from '@/preferences.js';
export function chooseFileFromPc(
multiple: boolean,
@@ -20,8 +20,8 @@ export function chooseFileFromPc(
nameConverter?: (file: File) => string | undefined;
},
): Promise<Misskey.entities.DriveFile[]> {
const uploadFolder = options?.uploadFolder ?? defaultStore.state.uploadFolder;
const keepOriginal = options?.keepOriginal ?? defaultStore.state.keepOriginalUploading;
const uploadFolder = options?.uploadFolder ?? prefer.s.uploadFolder;
const keepOriginal = options?.keepOriginal ?? prefer.s.keepOriginalUploading;
const nameConverter = options?.nameConverter ?? (() => undefined);
return new Promise((res, rej) => {
@@ -82,7 +82,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
misskeyApi('drive/files/upload-from-url', {
url: url,
folderId: defaultStore.state.uploadFolder,
folderId: prefer.s.uploadFolder,
marker,
});
@@ -96,7 +96,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
return new Promise((res, rej) => {
const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
const keepOriginal = ref(prefer.s.keepOriginalUploading);
os.popupMenu([label ? {
text: label,
+12 -11
View File
@@ -3,8 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { SoundStore } from '@/store.js';
import { defaultStore } from '@/store.js';
import type { SoundStore } from '@/preferences/def.js';
import { prefer } from '@/preferences.js';
import { PREF_DEF } from '@/preferences/def.js';
let ctx: AudioContext;
const cache = new Map<string, AudioBuffer>();
@@ -127,11 +128,11 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; })
* @param type スプライトの種類を指定
*/
export function playMisskeySfx(operationType: OperationType) {
const sound = defaultStore.state[`sound_${operationType}`];
const sound = prefer.s[`sound.on.${operationType}`];
playMisskeySfxFile(sound).then((succeed) => {
if (!succeed && sound.type === '_driveFile_') {
// ドライブファイルが存在しない場合はデフォルトのサウンドを再生する
const soundName = defaultStore.def[`sound_${operationType}`].default.type as Exclude<SoundType, '_driveFile_'>;
const soundName = PREF_DEF[`sound_${operationType}`].default.type as Exclude<SoundType, '_driveFile_'>;
if (_DEV_) console.log(`Failed to play sound: ${sound.fileUrl}, so play default sound: ${soundName}`);
playMisskeySfxFileInternal({
type: soundName,
@@ -166,7 +167,7 @@ async function playMisskeySfxFileInternal(soundStore: SoundStore): Promise<boole
if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
return false;
}
const masterVolume = defaultStore.state.sound_masterVolume;
const masterVolume = prefer.s['sound.masterVolume'];
if (isMute() || masterVolume === 0 || soundStore.volume === 0) {
return true; // ミュート時は成功として扱う
}
@@ -198,10 +199,10 @@ export function createSourceNode(buffer: AudioBuffer, opts: {
pan?: number;
playbackRate?: number;
}): {
soundSource: AudioBufferSourceNode;
panNode: StereoPannerNode;
gainNode: GainNode;
} {
soundSource: AudioBufferSourceNode;
panNode: StereoPannerNode;
gainNode: GainNode;
} {
const panNode = ctx.createStereoPanner();
panNode.pan.value = opts.pan ?? 0;
@@ -242,13 +243,13 @@ export async function getSoundDuration(file: string): Promise<number> {
* ミュートすべきかどうかを判断する
*/
export function isMute(): boolean {
if (defaultStore.state.sound_notUseSound) {
if (prefer.s['sound.notUseSound']) {
// サウンドを出力しない
return true;
}
// noinspection RedundantIfStatementJS
if (defaultStore.state.sound_useSoundOnlyWhenActive && document.visibilityState === 'hidden') {
if (prefer.s['sound.useSoundOnlyWhenActive'] && document.visibilityState === 'hidden') {
// ブラウザがアクティブな時のみサウンドを出力する
return true;
}
+32
View File
@@ -7,10 +7,12 @@ import { ref } from 'vue';
import tinycolor from 'tinycolor2';
import lightTheme from '@@/themes/_light.json5';
import darkTheme from '@@/themes/_dark.json5';
import JSON5 from 'json5';
import { deepClone } from './clone.js';
import type { BundledTheme } from 'shiki/themes';
import { globalEvents } from '@/events.js';
import { miLocalStorage } from '@/local-storage.js';
import { addTheme, getThemes } from '@/theme-store.js';
export type Theme = {
id: string;
@@ -101,6 +103,7 @@ export function applyTheme(theme: Theme, persist = true) {
if (persist) {
miLocalStorage.setItem('theme', JSON.stringify(props));
miLocalStorage.setItem('themeId', theme.id);
miLocalStorage.setItem('colorScheme', colorScheme);
}
@@ -155,3 +158,32 @@ export function validateTheme(theme: Record<string, any>): boolean {
if (theme.props == null || typeof theme.props !== 'object') return false;
return true;
}
export function parseThemeCode(code: string): Theme {
let theme;
try {
theme = JSON5.parse(code);
} catch (err) {
throw new Error('Failed to parse theme json');
}
if (!validateTheme(theme)) {
throw new Error('This theme is invaild');
}
if (getThemes().some(t => t.id === theme.id)) {
throw new Error('This theme is already installed');
}
return theme;
}
export function previewTheme(code: string): void {
const theme = parseThemeCode(code);
if (theme) applyTheme(theme, false);
}
export async function installTheme(code: string): Promise<void> {
const theme = parseThemeCode(code);
if (!theme) return;
await addTheme(theme);
}
+4 -4
View File
@@ -7,13 +7,13 @@ import { reactive, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { v4 as uuid } from 'uuid';
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
import { getCompressionConfig } from './upload/compress-config.js';
import { defaultStore } from '@/store.js';
import { apiUrl } from '@@/js/config.js';
import { getCompressionConfig } from './upload/compress-config.js';
import { $i } from '@/account.js';
import { alert } from '@/os.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { prefer } from '@/preferences.js';
type Uploading = {
id: string;
@@ -34,7 +34,7 @@ export function uploadFile(
file: File,
folder?: string | Misskey.entities.DriveFolder,
name?: string,
keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
keepOriginal: boolean = prefer.s.keepOriginalUploading,
): Promise<Misskey.entities.DriveFile> {
if ($i == null) throw new Error('Not logged in');
@@ -59,7 +59,7 @@ export function uploadFile(
const ctx = reactive<Uploading>({
id,
name: defaultStore.state.keepOriginalFilename ? filename : id + extension,
name: prefer.s.keepOriginalFilename ? filename : id + extension,
progressMax: undefined,
progressValue: undefined,
img: window.URL.createObjectURL(file),