Merge pull request MisskeyIO#412 from merge-upstream

This commit is contained in:
まっちゃとーにゅ 2024-02-07 00:57:19 +09:00 committed by GitHub
commit 7415eaffd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 311 additions and 117 deletions

View file

@ -49,6 +49,12 @@
- Enhance: MFMの属性でオートコンプリートが使用できるように #12735 - Enhance: MFMの属性でオートコンプリートが使用できるように #12735
- Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
- Enhance: リモートのユーザーはメニューから直接リモートで表示できるように - Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
- Enhance: リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように
- Enhance: コードのシンタックスハイライトにテーマを適用できるように
- Enhance: リアクション権限がない場合、ハートにフォールバックするのではなくリアクションピッカーなどから打てないように
- リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合
- センシティブなリアクションを認めていないユーザーにセンシティブなカスタム絵文字をリアクションしようとした場合
- ロールが必要な絵文字をリアクションしようとした場合
- Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: ネイティブモードの絵文字がモノクロにならないように
- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
- Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
@ -65,6 +71,8 @@
- Fix: 画像をクロップするとクロップ後の解像度が異様に低くなる問題の修正 - Fix: 画像をクロップするとクロップ後の解像度が異様に低くなる問題の修正
- Fix: 画像をクロップ時、正常に完了できない問題の修正 - Fix: 画像をクロップ時、正常に完了できない問題の修正
- Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正 - Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正
- Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
- Fix: エラー画像URLを設定した後解除するとデフォルトの画像が表示されない問題の修正
### Server ### Server
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました

View file

@ -625,6 +625,7 @@ export class ApRendererService {
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1', 'https://w3id.org/security/v1',
{ {
Key: 'sec:Key',
// as non-standards // as non-standards
manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
sensitive: 'as:sensitive', sensitive: 'as:sensitive',

View file

@ -33,6 +33,7 @@ export class EmojiEntityService {
category: emoji.category, category: emoji.category,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ) // || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl, url: emoji.publicUrl || emoji.originalUrl,
localOnly: emoji.localOnly ? true : undefined,
isSensitive: emoji.isSensitive ? true : undefined, isSensitive: emoji.isSensitive ? true : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined, roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
roleIdsThatCanNotBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction : undefined, roleIdsThatCanNotBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction : undefined,

View file

@ -27,6 +27,10 @@ export const packedEmojiSimpleSchema = {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
}, },
localOnly: {
type: 'boolean',
optional: true, nullable: false,
},
isSensitive: { isSensitive: {
type: 'boolean', type: 'boolean',
optional: true, nullable: false, optional: true, nullable: false,

View file

@ -5,14 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<template> <template>
<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }]" v-html="html"></div> <div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }, (darkMode ? $style.dark : $style.light)]" v-html="html"></div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import { bundledLanguagesInfo } from 'shiki'; import { bundledLanguagesInfo } from 'shiki';
import type { BuiltinLanguage } from 'shiki'; import type { BuiltinLanguage } from 'shiki';
import { getHighlighter } from '@/scripts/code-highlighter.js'; import { getHighlighter, getTheme } from '@/scripts/code-highlighter.js';
import { defaultStore } from '@/store.js';
const props = defineProps<{ const props = defineProps<{
code: string; code: string;
@ -21,11 +22,23 @@ const props = defineProps<{
}>(); }>();
const highlighter = await getHighlighter(); const highlighter = await getHighlighter();
const darkMode = defaultStore.reactiveState.darkMode;
const codeLang = ref<BuiltinLanguage | 'aiscript'>('js'); const codeLang = ref<BuiltinLanguage | 'aiscript'>('js');
const [lightThemeName, darkThemeName] = await Promise.all([
getTheme('light', true),
getTheme('dark', true),
]);
const html = computed(() => highlighter.codeToHtml(props.code, { const html = computed(() => highlighter.codeToHtml(props.code, {
lang: codeLang.value, lang: codeLang.value,
theme: 'dark-plus', themes: {
fallback: 'dark-plus',
light: lightThemeName,
dark: darkThemeName,
},
defaultColor: false,
cssVariablePrefix: '--shiki-',
})); }));
async function fetchLanguage(to: string): Promise<void> { async function fetchLanguage(to: string): Promise<void> {
@ -63,6 +76,15 @@ watch(() => props.lang, (to) => {
margin: .5em 0; margin: .5em 0;
overflow: auto; overflow: auto;
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--divider);
color: var(--shiki-fallback);
background-color: var(--shiki-fallback-bg);
& span {
color: var(--shiki-fallback);
background-color: var(--shiki-fallback-bg);
}
& pre, & pre,
& code { & code {
@ -70,6 +92,26 @@ watch(() => props.lang, (to) => {
} }
} }
.light.codeBlockRoot :global(.shiki) {
color: var(--shiki-light);
background-color: var(--shiki-light-bg);
& span {
color: var(--shiki-light);
background-color: var(--shiki-light-bg);
}
}
.dark.codeBlockRoot :global(.shiki) {
color: var(--shiki-dark);
background-color: var(--shiki-dark-bg);
& span {
color: var(--shiki-dark);
background-color: var(--shiki-dark-bg);
}
}
.codeBlockRoot.codeEditor { .codeBlockRoot.codeEditor {
min-width: 100%; min-width: 100%;
height: 100%; height: 100%;
@ -78,6 +120,7 @@ watch(() => props.lang, (to) => {
padding: 12px; padding: 12px;
margin: 0; margin: 0;
border-radius: 6px; border-radius: 6px;
border: none;
min-height: 130px; min-height: 130px;
pointer-events: none; pointer-events: none;
min-width: calc(100% - 24px); min-width: calc(100% - 24px);
@ -89,6 +132,11 @@ watch(() => props.lang, (to) => {
text-rendering: inherit; text-rendering: inherit;
text-transform: inherit; text-transform: inherit;
white-space: pre; white-space: pre;
& span {
display: inline-block;
min-height: 1em;
}
} }
} }
</style> </style>

View file

@ -53,7 +53,6 @@ function copy() {
} }
.codeBlockCopyButton { .codeBlockCopyButton {
color: #D4D4D4;
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 8px; right: 8px;
@ -67,8 +66,7 @@ function copy() {
.codeBlockFallbackRoot { .codeBlockFallbackRoot {
display: block; display: block;
overflow-wrap: anywhere; overflow-wrap: anywhere;
color: #D4D4D4; background: var(--bg);
background: #1E1E1E;
padding: 1em; padding: 1em;
margin: .5em 0; margin: .5em 0;
overflow: auto; overflow: auto;
@ -91,8 +89,8 @@ function copy() {
border-radius: 8px; border-radius: 8px;
padding: 24px; padding: 24px;
margin-top: 4px; margin-top: 4px;
color: #D4D4D4; color: var(--fg);
background: #1E1E1E; background: var(--bg);
} }
.codePlaceholderContainer { .codePlaceholderContainer {

View file

@ -198,10 +198,11 @@ watch(v, newValue => {
resize: none; resize: none;
text-align: left; text-align: left;
color: transparent; color: transparent;
caret-color: rgb(225, 228, 232); caret-color: var(--fg);
background-color: transparent; background-color: transparent;
border: 0; border: 0;
border-radius: 6px; border-radius: 6px;
box-sizing: border-box;
outline: 0; outline: 0;
min-width: calc(100% - 24px); min-width: calc(100% - 24px);
height: 100%; height: 100%;
@ -212,6 +213,6 @@ watch(v, newValue => {
} }
.textarea::selection { .textarea::selection {
color: #fff; color: var(--bg);
} }
</style> </style>

View file

@ -18,8 +18,7 @@ const props = defineProps<{
display: inline-block; display: inline-block;
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
overflow-wrap: anywhere; overflow-wrap: anywhere;
color: #D4D4D4; background: var(--bg);
background: #1E1E1E;
padding: .1em; padding: .1em;
border-radius: .3em; border-radius: .3em;
} }

View file

@ -118,6 +118,7 @@ import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js'; import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
showPinned?: boolean; showPinned?: boolean;
@ -126,6 +127,7 @@ const props = withDefaults(defineProps<{
asDrawer?: boolean; asDrawer?: boolean;
asWindow?: boolean; asWindow?: boolean;
asReactionPicker?: boolean; // 使使 asReactionPicker?: boolean; // 使使
targetNote?: Misskey.entities.Note;
}>(), { }>(), {
showPinned: true, showPinned: true,
}); });
@ -344,8 +346,7 @@ watch(q, () => {
}); });
function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean { function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean {
return ((emoji.roleIdsThatCanBeUsedThisEmojiAsReaction === undefined || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || (!!$i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction?.includes(r.id)))) && return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji);
((emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction === undefined || emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction.length === 0) || (!!$i && !$i.roles.some(r => emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction?.includes(r.id))));
} }
function focus() { function focus() {

View file

@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:showPinned="showPinned" :showPinned="showPinned"
:pinnedEmojis="pinnedEmojis" :pinnedEmojis="pinnedEmojis"
:asReactionPicker="asReactionPicker" :asReactionPicker="asReactionPicker"
:targetNote="targetNote"
:asDrawer="type === 'drawer'" :asDrawer="type === 'drawer'"
:max-height="maxHeight" :max-height="maxHeight"
@chosen="chosen" @chosen="chosen"
@ -32,6 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { shallowRef } from 'vue'; import { shallowRef } from 'vue';
import MkModal from '@/components/MkModal.vue'; import MkModal from '@/components/MkModal.vue';
import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
@ -43,6 +45,7 @@ const props = withDefaults(defineProps<{
showPinned?: boolean; showPinned?: boolean;
pinnedEmojis?: string[], pinnedEmojis?: string[],
asReactionPicker?: boolean; asReactionPicker?: boolean;
targetNote?: Misskey.entities.Note;
choseAndClose?: boolean; choseAndClose?: boolean;
}>(), { }>(), {
manualShowing: null, manualShowing: null,

View file

@ -18,6 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:showPinned="showPinned" :showPinned="showPinned"
:pinnedEmojis="pinnedEmojis" :pinnedEmojis="pinnedEmojis"
:asReactionPicker="asReactionPicker" :asReactionPicker="asReactionPicker"
:targetNote="targetNote"
asWindow asWindow
@chosen="chosen" @chosen="chosen"
/> />
@ -25,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as Misskey from 'misskey-js';
import MkWindow from '@/components/MkWindow.vue'; import MkWindow from '@/components/MkWindow.vue';
import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
@ -33,6 +35,7 @@ withDefaults(defineProps<{
showPinned?: boolean; showPinned?: boolean;
pinnedEmojis?: string[], pinnedEmojis?: string[],
asReactionPicker?: boolean; asReactionPicker?: boolean;
targetNote?: Misskey.entities.Note
}>(), { }>(), {
showPinned: true, showPinned: true,
}); });

View file

@ -119,6 +119,7 @@ function close() {
margin-top: 12px; margin-top: 12px;
font-size: 0.8em; font-size: 0.8em;
line-height: 1.5em; line-height: 1.5em;
text-align: center;
} }
> .indicatorWithValue { > .indicatorWithValue {

View file

@ -247,7 +247,7 @@ const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entiti
const isMyRenote = $i && ($i.id === note.value.userId); const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false); const showContent = ref(false);
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null); const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value) : null); const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
const isLong = shouldCollapsed(appearNote.value, urls.value ?? []); const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
const collapsed = ref(appearNote.value.cw == null && isLong); const collapsed = ref(appearNote.value.cw == null && isLong);
const isDeleted = ref(false); const isDeleted = ref(false);
@ -379,7 +379,7 @@ function react(viaKeyboard = false): void {
} }
} else { } else {
blur(); blur();
reactionPicker.show(reactButton.value ?? null, reaction => { reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
sound.playMisskeySfx('reaction'); sound.playMisskeySfx('reaction');
if (props.mock) { if (props.mock) {

View file

@ -278,7 +278,7 @@ const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : fals
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null); const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false); const translating = ref(false);
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null; const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
const urls = parsed ? extractUrlFromMfm(parsed) : null; const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
const conversation = ref<Misskey.entities.Note[]>([]); const conversation = ref<Misskey.entities.Note[]>([]);
const replies = ref<Misskey.entities.Note[]>([]); const replies = ref<Misskey.entities.Note[]>([]);
@ -386,7 +386,7 @@ function react(viaKeyboard = false): void {
} }
} else { } else {
blur(); blur();
reactionPicker.show(reactButton.value ?? null, reaction => { reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
sound.playMisskeySfx('reaction'); sound.playMisskeySfx('reaction');
misskeyApi('notes/reactions/create', { misskeyApi('notes/reactions/create', {

View file

@ -32,6 +32,8 @@ import { claimAchievement } from '@/scripts/achievements.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';
import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
import { customEmojis } from '@/custom-emojis.js';
const props = defineProps<{ const props = defineProps<{
reaction: string; reaction: string;
@ -48,13 +50,19 @@ const emit = defineEmits<{
const buttonEl = shallowRef<HTMLElement>(); const buttonEl = shallowRef<HTMLElement>();
const canToggle = computed(() => !props.reaction.match(/@\w/) && $i); const isCustomEmoji = computed(() => props.reaction.includes(':'));
const emoji = computed(() => isCustomEmoji.value ? customEmojis.value.find(emoji => emoji.name === props.reaction.replace(/:/g, '').replace(/@\./, '')) : null);
const canToggle = computed(() => {
return !props.reaction.match(/@\w/) && $i
&& (emoji.value && checkReactionPermissions($i, props.note, emoji.value))
|| !isCustomEmoji.value;
});
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
async function toggleReaction() { async function toggleReaction() {
if (!canToggle.value) return; if (!canToggle.value) return;
// TODO: 使
const oldReaction = props.note.myReaction; const oldReaction = props.note.myReaction;
if (oldReaction) { if (oldReaction) {
const confirm = await os.confirm({ const confirm = await os.confirm({
@ -101,8 +109,8 @@ async function toggleReaction() {
} }
async function menu(ev) { async function menu(ev) {
if (!canToggle.value) return; if (!canGetInfo.value) return;
if (!props.reaction.includes(':')) return;
os.popupMenu([{ os.popupMenu([{
text: i18n.ts.info, text: i18n.ts.info,
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',

View file

@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div :class="$style.caption"><slot name="caption"></slot></div> <div :class="$style.caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" primary @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> <MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
</div> </div>
</template> </template>
@ -141,6 +141,7 @@ function show() {
active: computed(() => v.value === option.props?.value), active: computed(() => v.value === option.props?.value),
action: () => { action: () => {
v.value = option.props?.value; v.value = option.props?.value;
changed.value = true;
emit('changeByUser', v.value); emit('changeByUser', v.value);
}, },
}); });
@ -291,6 +292,10 @@ function show() {
padding-left: 6px; padding-left: 6px;
} }
.save {
margin: 8px 0 0 0;
}
.chevron { .chevron {
transition: transform 0.1s ease-out; transition: transform 0.1s ease-out;
} }

View file

@ -4,7 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<KeepAlive :max="defaultStore.state.numberOfPageCache"> <KeepAlive
:max="defaultStore.state.numberOfPageCache"
:exclude="pageCacheController"
>
<Suspense :timeout="0"> <Suspense :timeout="0">
<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/> <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
@ -16,9 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onBeforeUnmount, provide, ref, shallowRef } from 'vue'; import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
import { IRouter, Resolved } from '@/nirax.js'; import { IRouter, Resolved } from '@/nirax.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { globalEvents } from '@/events.js';
import MkLoadingPage from '@/pages/_loading_.vue';
const props = defineProps<{ const props = defineProps<{
router?: IRouter; router?: IRouter;
@ -46,20 +51,47 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
} }
const current = resolveNested(router.current)!; const current = resolveNested(router.current)!;
const currentPageComponent = shallowRef(current.route.component); const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
const currentPageProps = ref(current.props); const currentPageProps = ref(current.props);
const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props))); const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
function onChange({ resolved, key: newKey }) { function onChange({ resolved, key: newKey }) {
const current = resolveNested(resolved); const current = resolveNested(resolved);
if (current == null) return; if (current == null || 'redirect' in current.route) return;
currentPageComponent.value = current.route.component; currentPageComponent.value = current.route.component;
currentPageProps.value = current.props; currentPageProps.value = current.props;
key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props)); key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props));
nextTick(() => {
//
if (clearCacheRequested.value) {
clearCacheRequested.value = false;
}
});
} }
router.addListener('change', onChange); router.addListener('change', onChange);
// #region
/**
* キャッシュクリアが有効になったら全キャッシュをクリアする
*
* keepAlive側にwatcherがあるのですぐ消えるとはおもうけど念のためページ遷移完了まではキャッシュを無効化しておく
* キャッシュ有効時向けにexcludeを使いたい場合はpageCacheControllerに並列に突っ込むのではなく下に追記すること
*/
const pageCacheController = computed(() => clearCacheRequested.value ? /.*/ : undefined);
const clearCacheRequested = ref(false);
globalEvents.on('requestClearPageCache', () => {
if (_DEV_) console.log('clear page cache requested');
if (!clearCacheRequested.value) {
clearCacheRequested.value = true;
}
});
// #endregion
onBeforeUnmount(() => { onBeforeUnmount(() => {
router.removeListener('change', onChange); router.removeListener('change', onChange);
}); });

View file

@ -4,6 +4,10 @@
*/ */
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import * as Misskey from 'misskey-js';
// TODO: 型付け export const globalEvents = new EventEmitter<{
export const globalEvents = new EventEmitter(); themeChanged: () => void;
clientNotification: (notification: Misskey.entities.Notification) => void;
requestClearPageCache: () => void;
}>();

View file

@ -148,9 +148,9 @@ function save() {
themeColor: themeColor.value === '' ? null : themeColor.value, themeColor: themeColor.value === '' ? null : themeColor.value,
defaultLightTheme: defaultLightTheme.value === '' ? null : defaultLightTheme.value, defaultLightTheme: defaultLightTheme.value === '' ? null : defaultLightTheme.value,
defaultDarkTheme: defaultDarkTheme.value === '' ? null : defaultDarkTheme.value, defaultDarkTheme: defaultDarkTheme.value === '' ? null : defaultDarkTheme.value,
infoImageUrl: infoImageUrl.value, infoImageUrl: infoImageUrl.value === '' ? null : infoImageUrl.value,
notFoundImageUrl: notFoundImageUrl.value, notFoundImageUrl: notFoundImageUrl.value === '' ? null : notFoundImageUrl.value,
serverErrorImageUrl: serverErrorImageUrl.value, serverErrorImageUrl: serverErrorImageUrl.value === '' ? null : serverErrorImageUrl.value,
manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)), manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)),
}).then(() => { }).then(() => {
fetchInstance(); fetchInstance();

View file

@ -893,7 +893,6 @@ function getGameImageDriveFile() {
formData.append('file', blob); formData.append('file', blob);
formData.append('name', `bubble-game-${Date.now()}.png`); formData.append('name', `bubble-game-${Date.now()}.png`);
formData.append('isSensitive', 'false'); formData.append('isSensitive', 'false');
formData.append('comment', 'null');
formData.append('i', $i.token); formData.append('i', $i.token);
if (defaultStore.state.uploadFolder) { if (defaultStore.state.uploadFolder) {
formData.append('folderId', defaultStore.state.uploadFolder); formData.append('folderId', defaultStore.state.uploadFolder);

View file

@ -157,7 +157,7 @@ const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev);
const setDefaultEmoji = () => setDefault(pinnedEmojis); const setDefaultEmoji = () => setDefault(pinnedEmojis);
function previewReaction(ev: MouseEvent) { function previewReaction(ev: MouseEvent) {
reactionPicker.show(getHTMLElement(ev)); reactionPicker.show(getHTMLElement(ev), null);
} }
function previewEmoji(ev: MouseEvent) { function previewEmoji(ev: MouseEvent) {

View file

@ -125,6 +125,7 @@ import { langmap } from '@/scripts/langmap.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import { claimAchievement } from '@/scripts/achievements.js'; import { claimAchievement } from '@/scripts/achievements.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { globalEvents } from '@/events.js';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@ -173,6 +174,7 @@ function saveFields() {
os.apiWithDialog('i/update', { os.apiWithDialog('i/update', {
fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })), fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
}); });
globalEvents.emit('requestClearPageCache');
} }
function save() { function save() {
@ -191,6 +193,7 @@ function save() {
isBot: !!profile.isBot, isBot: !!profile.isBot,
isCat: !!profile.isCat, isCat: !!profile.isCat,
}); });
globalEvents.emit('requestClearPageCache');
claimAchievement('profileFilled'); claimAchievement('profileFilled');
if (profile.name === 'syuilo' || profile.name === 'しゅいろ') { if (profile.name === 'syuilo' || profile.name === 'しゅいろ') {
claimAchievement('setNameToSyuilo'); claimAchievement('setNameToSyuilo');
@ -222,6 +225,7 @@ function changeAvatar(ev) {
}); });
$i.avatarId = i.avatarId; $i.avatarId = i.avatarId;
$i.avatarUrl = i.avatarUrl; $i.avatarUrl = i.avatarUrl;
globalEvents.emit('requestClearPageCache');
claimAchievement('profileFilled'); claimAchievement('profileFilled');
}); });
} }
@ -248,6 +252,7 @@ function changeBanner(ev) {
}); });
$i.bannerId = i.bannerId; $i.bannerId = i.bannerId;
$i.bannerUrl = i.bannerUrl; $i.bannerUrl = i.bannerUrl;
globalEvents.emit('requestClearPageCache');
}); });
} }

View file

@ -88,6 +88,18 @@ import { uniqueBy } from '@/scripts/array.js';
import { fetchThemes, getThemes } from '@/theme-store.js'; import { fetchThemes, getThemes } from '@/theme-store.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import * as os from '@/os.js';
async function reloadAsk() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
unisonReload();
}
const installedThemes = ref(getThemes()); const installedThemes = ref(getThemes());
const builtinThemes = getBuiltinThemesRef(); const builtinThemes = getBuiltinThemesRef();
@ -124,6 +136,7 @@ const lightThemeId = computed({
} }
}, },
}); });
const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
const wallpaper = ref(miLocalStorage.getItem('wallpaper')); const wallpaper = ref(miLocalStorage.getItem('wallpaper'));
@ -141,7 +154,7 @@ watch(wallpaper, () => {
} else { } else {
miLocalStorage.setItem('wallpaper', wallpaper.value); miLocalStorage.setItem('wallpaper', wallpaper.value);
} }
location.reload(); reloadAsk();
}); });
onActivated(() => { onActivated(() => {

View file

@ -13,6 +13,7 @@ import { get, set } from '@/scripts/idb-proxy.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { useStream } from '@/stream.js'; import { useStream } from '@/stream.js';
import { deepClone } from '@/scripts/clone.js'; import { deepClone } from '@/scripts/clone.js';
import { deepMerge } from '@/scripts/merge.js';
type StateDef = Record<string, { type StateDef = Record<string, {
where: 'account' | 'device' | 'deviceAccount'; where: 'account' | 'device' | 'deviceAccount';
@ -84,29 +85,9 @@ export class Storage<T extends StateDef> {
return typeof value === 'object' && value !== null && !Array.isArray(value); return typeof value === 'object' && value !== null && !Array.isArray(value);
} }
/**
* valueにないキーをdefからもらう\
* nullはそのままundefinedはdefの値
**/
private mergeObject<X>(value: X, def: X): X {
if (this.isPureObject(value) && this.isPureObject(def)) {
const result = structuredClone(value) as X;
for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
result[k] = v;
} else if (this.isPureObject(v) && this.isPureObject(result[k])) {
const child = structuredClone(result[k]) as X[keyof X] & Record<string | number | symbol, unknown>;
result[k] = this.mergeObject<typeof v>(child, v);
}
}
return result;
}
return value;
}
private mergeState<X>(value: X, def: X): X { private mergeState<X>(value: X, def: X): X {
if (this.isPureObject(value) && this.isPureObject(def)) { if (this.isPureObject(value) && this.isPureObject(def)) {
const merged = this.mergeObject(value, def); const merged = deepMerge(value, def);
if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged); if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged);
@ -258,7 +239,7 @@ export class Storage<T extends StateDef> {
/** /**
* getter/setterを作ります * getter/setterを作ります
* vueで設定コントロールのmodelとして使う用 * vueで設定コントロールのmodelとして使う用
*/ */
public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): { public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): {
get: () => T[K]['default']; get: () => T[K]['default'];

View file

@ -80,6 +80,10 @@ class MainRouterProxy implements IRouter {
return this.supplier().resolve(path); return this.supplier().resolve(path);
} }
init(): void {
this.supplier().init();
}
eventNames(): Array<EventEmitter.EventNames<RouterEvent>> { eventNames(): Array<EventEmitter.EventNames<RouterEvent>> {
return this.supplier().eventNames(); return this.supplier().eventNames();
} }

View file

@ -0,0 +1,11 @@
import * as Misskey from 'misskey-js';
export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple): boolean {
const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [];
const roleIdsThatCanNotBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction ?? [];
return !(emoji.localOnly && note.user.host !== me.host)
&& !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote'))
&& (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id)))
&& (roleIdsThatCanNotBeUsedThisEmojiAsReaction.length === 0 || !me.roles.some(role => roleIdsThatCanNotBeUsedThisEmojiAsReaction.includes(role.id)));
}

View file

@ -8,13 +8,13 @@
// あと、Vue RefをIndexedDBに保存しようとしてstructredCloneを使ったらエラーになった // あと、Vue RefをIndexedDBに保存しようとしてstructredCloneを使ったらエラーになった
// https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045 // https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045
type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[]; export type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | { [key: number]: Cloneable } | { [key: symbol]: Cloneable } | Cloneable[];
export function deepClone<T extends Cloneable>(x: T): T { export function deepClone<T extends Cloneable>(x: T): T {
if (typeof x === 'object') { if (typeof x === 'object') {
if (x === null) return x; if (x === null) return x;
if (Array.isArray(x)) return x.map(deepClone) as T; if (Array.isArray(x)) return x.map(deepClone) as T;
const obj = {} as Record<string, Cloneable>; const obj = {} as Record<string | number | symbol, Cloneable>;
for (const [k, v] of Object.entries(x)) { for (const [k, v] of Object.entries(x)) {
obj[k] = v === undefined ? undefined : deepClone(v); obj[k] = v === undefined ? undefined : deepClone(v);
} }

View file

@ -1,9 +1,51 @@
import { bundledThemesInfo } from 'shiki';
import { getHighlighterCore, loadWasm } from 'shiki/core'; import { getHighlighterCore, loadWasm } from 'shiki/core';
import darkPlus from 'shiki/themes/dark-plus.mjs'; import darkPlus from 'shiki/themes/dark-plus.mjs';
import type { Highlighter, LanguageRegistration } from 'shiki'; import { unique } from './array.js';
import { deepClone } from './clone.js';
import { deepMerge } from './merge.js';
import type { Highlighter, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki';
import { ColdDeviceStorage } from '@/store.js';
import lightTheme from '@/themes/_light.json5';
import darkTheme from '@/themes/_dark.json5';
let _highlighter: Highlighter | null = null; let _highlighter: Highlighter | 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'));
if (theme.base) {
const base = [lightTheme, darkTheme].find(x => x.id === theme.base);
if (base?.codeHighlighter) theme.codeHighlighter = { ...base.codeHighlighter, ...theme.codeHighlighter };
}
if (theme.codeHighlighter) {
let _res: ThemeRegistration = {};
if (theme.codeHighlighter.base === '_none_') {
_res = deepClone(theme.codeHighlighter.overrides);
} else {
const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus;
_res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base);
}
if (_res.name == null) {
_res.name = theme.id;
}
_res.type = mode;
if (getName) {
return _res.name;
}
return _res;
}
if (getName) {
return 'dark-plus';
}
return darkPlus;
}
export async function getHighlighter(): Promise<Highlighter> { export async function getHighlighter(): Promise<Highlighter> {
if (!_highlighter) { if (!_highlighter) {
return await initHighlighter(); return await initHighlighter();
@ -16,8 +58,14 @@ export async function initHighlighter() {
await loadWasm(import('shiki/onig.wasm?init')); await loadWasm(import('shiki/onig.wasm?init'));
// テーマの重複を消す
const themes = unique([
darkPlus,
...(await Promise.all([getTheme('light'), getTheme('dark')])),
]);
const highlighter = await getHighlighterCore({ const highlighter = await getHighlighterCore({
themes: [darkPlus], themes,
langs: [ langs: [
import('shiki/langs/javascript.mjs'), import('shiki/langs/javascript.mjs'),
{ {
@ -27,6 +75,20 @@ export 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);
}
});
_highlighter = highlighter; _highlighter = highlighter;
return highlighter; return highlighter;

View file

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { deepClone } from './clone.js';
import type { Cloneable } from './clone.js';
function isPureObject(value: unknown): value is Record<string | number | symbol, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
/**
* valueにないキーをdefからもらう\
* nullはそのままundefinedはdefの値
**/
export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: X, def: X): X {
if (isPureObject(value) && isPureObject(def)) {
const result = deepClone(value as Cloneable) as X;
for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
result[k] = v;
} else if (isPureObject(v) && isPureObject(result[k])) {
const child = deepClone(result[k] as Cloneable) as X[keyof X] & Record<string | number | symbol, unknown>;
result[k] = deepMerge<typeof v>(child, v);
}
}
return result;
}
return value;
}

View file

@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import * as Misskey from 'misskey-js';
import { defineAsyncComponent, Ref, ref } from 'vue'; import { defineAsyncComponent, Ref, ref } from 'vue';
import { popup } from '@/os.js'; import { popup } from '@/os.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
@ -10,6 +11,7 @@ import { defaultStore } from '@/store.js';
class ReactionPicker { class ReactionPicker {
private src: Ref<HTMLElement | null> = ref(null); private src: Ref<HTMLElement | null> = ref(null);
private manualShowing = ref(false); private manualShowing = ref(false);
private targetNote: Ref<Misskey.entities.Note | null> = ref(null);
private onChosen?: (reaction: string) => void; private onChosen?: (reaction: string) => void;
private onClosed?: () => void; private onClosed?: () => void;
@ -23,6 +25,7 @@ class ReactionPicker {
src: this.src, src: this.src,
pinnedEmojis: reactionsRef, pinnedEmojis: reactionsRef,
asReactionPicker: true, asReactionPicker: true,
targetNote: this.targetNote,
manualShowing: this.manualShowing, manualShowing: this.manualShowing,
}, { }, {
done: reaction => { done: reaction => {
@ -38,8 +41,9 @@ class ReactionPicker {
}); });
} }
public show(src: HTMLElement | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
this.src.value = src; this.src.value = src;
this.targetNote.value = targetNote;
this.manualShowing.value = true; this.manualShowing.value = true;
this.onChosen = onChosen; this.onChosen = onChosen;
this.onClosed = onClosed; this.onClosed = onClosed;

View file

@ -6,6 +6,7 @@
import { ref } from 'vue'; import { ref } from 'vue';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { deepClone } from './clone.js'; import { deepClone } from './clone.js';
import type { BuiltinTheme } from 'shiki';
import { globalEvents } from '@/events.js'; import { globalEvents } from '@/events.js';
import lightTheme from '@/themes/_light.json5'; import lightTheme from '@/themes/_light.json5';
import darkTheme from '@/themes/_dark.json5'; import darkTheme from '@/themes/_dark.json5';
@ -18,6 +19,13 @@ export type Theme = {
desc?: string; desc?: string;
base?: 'dark' | 'light'; base?: 'dark' | 'light';
props: Record<string, string>; props: Record<string, string>;
codeHighlighter?: {
base: BuiltinTheme;
overrides?: Record<string, any>;
} | {
base: '_none_';
overrides: Record<string, any>;
};
}; };
export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
@ -53,7 +61,7 @@ export const getBuiltinThemesRef = () => {
return builtinThemes; return builtinThemes;
}; };
let timeout = null; let timeout: number | null = null;
export function applyTheme(theme: Theme, persist = true) { export function applyTheme(theme: Theme, persist = true) {
if (timeout) window.clearTimeout(timeout); if (timeout) window.clearTimeout(timeout);

View file

@ -94,4 +94,8 @@
X16: ':alpha<0.7<@panel', X16: ':alpha<0.7<@panel',
X17: ':alpha<0.8<@bg', X17: ':alpha<0.8<@bg',
}, },
codeHighlighter: {
base: 'one-dark-pro',
},
} }

View file

@ -94,4 +94,8 @@
X16: ':alpha<0.7<@panel', X16: ':alpha<0.7<@panel',
X17: ':alpha<0.8<@bg', X17: ':alpha<0.8<@bg',
}, },
codeHighlighter: {
base: 'catppuccin-latte',
},
} }

View file

@ -4,22 +4,6 @@ import { toPascal } from 'ts-case-convert';
import OpenAPIParser from '@readme/openapi-parser'; import OpenAPIParser from '@readme/openapi-parser';
import openapiTS from 'openapi-typescript'; import openapiTS from 'openapi-typescript';
function generateVersionHeaderComment(openApiDocs: OpenAPIV3_1.Document): string {
const contents = {
version: openApiDocs.info.version,
// generatedAt: new Date().toISOString(),
};
const lines: string[] = [];
lines.push('/*');
for (const [key, value] of Object.entries(contents)) {
lines.push(` * ${key}: ${value}`);
}
lines.push(' */');
return lines.join('\n');
}
async function generateBaseTypes( async function generateBaseTypes(
openApiDocs: OpenAPIV3_1.Document, openApiDocs: OpenAPIV3_1.Document,
openApiJsonPath: string, openApiJsonPath: string,
@ -36,9 +20,6 @@ async function generateBaseTypes(
} }
lines.push(''); lines.push('');
lines.push(generateVersionHeaderComment(openApiDocs));
lines.push('');
const generatedTypes = await openapiTS(openApiJsonPath, { exportType: true }); const generatedTypes = await openapiTS(openApiJsonPath, { exportType: true });
lines.push(generatedTypes); lines.push(generatedTypes);
lines.push(''); lines.push('');
@ -59,8 +40,6 @@ async function generateSchemaEntities(
const schemaNames = Object.keys(schemas); const schemaNames = Object.keys(schemas);
const typeAliasLines: string[] = []; const typeAliasLines: string[] = [];
typeAliasLines.push(generateVersionHeaderComment(openApiDocs));
typeAliasLines.push('');
typeAliasLines.push(`import { components } from '${toImportPath(typeFileName)}';`); typeAliasLines.push(`import { components } from '${toImportPath(typeFileName)}';`);
typeAliasLines.push( typeAliasLines.push(
...schemaNames.map(it => `export type ${it} = components['schemas']['${it}'];`), ...schemaNames.map(it => `export type ${it} = components['schemas']['${it}'];`),
@ -119,9 +98,6 @@ async function generateEndpoints(
const entitiesOutputLine: string[] = []; const entitiesOutputLine: string[] = [];
entitiesOutputLine.push(generateVersionHeaderComment(openApiDocs));
entitiesOutputLine.push('');
entitiesOutputLine.push(`import { operations } from '${toImportPath(typeFileName)}';`); entitiesOutputLine.push(`import { operations } from '${toImportPath(typeFileName)}';`);
entitiesOutputLine.push(''); entitiesOutputLine.push('');
@ -139,9 +115,6 @@ async function generateEndpoints(
const endpointOutputLine: string[] = []; const endpointOutputLine: string[] = [];
endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
endpointOutputLine.push('');
endpointOutputLine.push('import type {'); endpointOutputLine.push('import type {');
endpointOutputLine.push( endpointOutputLine.push(
...[emptyRequest, emptyResponse, ...entities].map(it => '\t' + it.generateName() + ','), ...[emptyRequest, emptyResponse, ...entities].map(it => '\t' + it.generateName() + ','),
@ -187,9 +160,6 @@ async function generateApiClientJSDoc(
const endpointOutputLine: string[] = []; const endpointOutputLine: string[] = [];
endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
endpointOutputLine.push('');
endpointOutputLine.push(`import type { SwitchCaseResponseType } from '${toImportPath(apiClientFileName)}';`); endpointOutputLine.push(`import type { SwitchCaseResponseType } from '${toImportPath(apiClientFileName)}';`);
endpointOutputLine.push(`import type { Endpoints } from '${toImportPath(endpointsFileName)}';`); endpointOutputLine.push(`import type { Endpoints } from '${toImportPath(endpointsFileName)}';`);
endpointOutputLine.push(''); endpointOutputLine.push('');

View file

@ -1,7 +1,3 @@
/*
* version: 2024.2.0-io
*/
import type { SwitchCaseResponseType } from '../api.js'; import type { SwitchCaseResponseType } from '../api.js';
import type { Endpoints } from './endpoint.js'; import type { Endpoints } from './endpoint.js';

View file

@ -1,7 +1,3 @@
/*
* version: 2024.2.0-io
*/
import type { import type {
EmptyRequest, EmptyRequest,
EmptyResponse, EmptyResponse,

View file

@ -1,7 +1,3 @@
/*
* version: 2024.2.0-io
*/
import { operations } from './types.js'; import { operations } from './types.js';
export type EmptyRequest = Record<string, unknown> | undefined; export type EmptyRequest = Record<string, unknown> | undefined;

View file

@ -1,7 +1,3 @@
/*
* version: 2024.2.0-io
*/
import { components } from './types.js'; import { components } from './types.js';
export type Error = components['schemas']['Error']; export type Error = components['schemas']['Error'];
export type UserLite = components['schemas']['UserLite']; export type UserLite = components['schemas']['UserLite'];

View file

@ -1,10 +1,6 @@
/* eslint @typescript-eslint/naming-convention: 0 */ /* eslint @typescript-eslint/naming-convention: 0 */
/* eslint @typescript-eslint/no-explicit-any: 0 */ /* eslint @typescript-eslint/no-explicit-any: 0 */
/*
* version: 2024.2.0-io
*/
/** /**
* This file was auto-generated by openapi-typescript. * This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file. * Do not make direct changes to the file.
@ -4520,6 +4516,7 @@ export type components = {
name: string; name: string;
category: string | null; category: string | null;
url: string; url: string;
localOnly?: boolean;
isSensitive?: boolean; isSensitive?: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction?: string[]; roleIdsThatCanBeUsedThisEmojiAsReaction?: string[];
roleIdsThatCanNotBeUsedThisEmojiAsReaction?: string[]; roleIdsThatCanNotBeUsedThisEmojiAsReaction?: string[];