Merge tag '2023.12.0' into merge-upstream

This commit is contained in:
riku6460
2023-12-23 20:22:38 +09:00
77 changed files with 2067 additions and 530 deletions

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-if="thereIsTreasure" class="_button treasure" @click="getTreasure"><img src="/fluent-emoji/1f3c6.png" class="treasureImg"></button>
</div>
<div style="text-align: center;">
{{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a>
{{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/about-misskey/" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a>
</div>
<div v-if="$i != null" style="text-align: center;">
<MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton>
@@ -139,73 +139,73 @@ import { $i } from '@/account.js';
const patronsWithIcon = [{
name: 'カイヤン',
icon: 'https://misskey-hub.net/patrons/a2820716883e408cb87773e377ce7c8d.jpg',
icon: 'https://assets.misskey-hub.net/patrons/a2820716883e408cb87773e377ce7c8d.jpg',
}, {
name: 'だれかさん',
icon: 'https://misskey-hub.net/patrons/f7409b5e5a88477a9b9d740c408de125.jpg',
icon: 'https://assets.misskey-hub.net/patrons/f7409b5e5a88477a9b9d740c408de125.jpg',
}, {
name: 'narazaka',
icon: 'https://misskey-hub.net/patrons/e3affff31ffb4877b1196c7360abc3e5.jpg',
icon: 'https://assets.misskey-hub.net/patrons/e3affff31ffb4877b1196c7360abc3e5.jpg',
}, {
name: 'ひとぅ',
icon: 'https://misskey-hub.net/patrons/8cc0d0a0a6d84c88bca1aedabf6ed5ab.jpg',
icon: 'https://assets.misskey-hub.net/patrons/8cc0d0a0a6d84c88bca1aedabf6ed5ab.jpg',
}, {
name: 'ぱーこ',
icon: 'https://misskey-hub.net/patrons/79c6602ffade489e8df2fcf2c2bc5d9d.jpg',
icon: 'https://assets.misskey-hub.net/patrons/79c6602ffade489e8df2fcf2c2bc5d9d.jpg',
}, {
name: 'わっほー☆',
icon: 'https://misskey-hub.net/patrons/d31d5d13924443a082f3da7966318a0a.jpg',
icon: 'https://assets.misskey-hub.net/patrons/d31d5d13924443a082f3da7966318a0a.jpg',
}, {
name: 'mollinaca',
icon: 'https://misskey-hub.net/patrons/ceb36b8f66e549bdadb3b90d5da62314.jpg',
icon: 'https://assets.misskey-hub.net/patrons/ceb36b8f66e549bdadb3b90d5da62314.jpg',
}, {
name: '坂本龍',
icon: 'https://misskey-hub.net/patrons/a631cf8b490145cf8dbbe4e7508cfbc2.jpg',
icon: 'https://assets.misskey-hub.net/patrons/a631cf8b490145cf8dbbe4e7508cfbc2.jpg',
}, {
name: 'takke',
icon: 'https://misskey-hub.net/patrons/6c3327e626c046f2914fbcd9f7557935.jpg',
icon: 'https://assets.misskey-hub.net/patrons/6c3327e626c046f2914fbcd9f7557935.jpg',
}, {
name: 'ぺんぎん',
icon: 'https://misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg',
icon: 'https://assets.misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg',
}, {
name: 'かみらえっと',
icon: 'https://misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg',
icon: 'https://assets.misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg',
}, {
name: 'へてて',
icon: 'https://misskey-hub.net/patrons/0431eacd7c6843d09de8ea9984307e86.jpg',
icon: 'https://assets.misskey-hub.net/patrons/0431eacd7c6843d09de8ea9984307e86.jpg',
}, {
name: 'spinlock',
icon: 'https://misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg',
icon: 'https://assets.misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg',
}, {
name: 'じゅくま',
icon: 'https://misskey-hub.net/patrons/3e56bdac69dd42f7a06e0f12cf2fc895.jpg',
icon: 'https://assets.misskey-hub.net/patrons/3e56bdac69dd42f7a06e0f12cf2fc895.jpg',
}, {
name: '清遊あみ',
icon: 'https://misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg',
icon: 'https://assets.misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg',
}, {
name: 'Nagi8410',
icon: 'https://misskey-hub.net/patrons/31b102ab4fc540ed806b0461575d38be.jpg',
icon: 'https://assets.misskey-hub.net/patrons/31b102ab4fc540ed806b0461575d38be.jpg',
}, {
name: '山岡士郎',
icon: 'https://misskey-hub.net/patrons/84b9056341684266bb1eda3e680d094d.jpg',
icon: 'https://assets.misskey-hub.net/patrons/84b9056341684266bb1eda3e680d094d.jpg',
}, {
name: 'よもやまたろう',
icon: 'https://misskey-hub.net/patrons/4273c9cce50d445f8f7d0f16113d6d7f.jpg',
icon: 'https://assets.misskey-hub.net/patrons/4273c9cce50d445f8f7d0f16113d6d7f.jpg',
}, {
name: '花咲ももか',
icon: 'https://misskey-hub.net/patrons/8c9b2b9128cb4fee99f04bb4f86f2efa.jpg',
icon: 'https://assets.misskey-hub.net/patrons/8c9b2b9128cb4fee99f04bb4f86f2efa.jpg',
}, {
name: 'カガミ',
icon: 'https://misskey-hub.net/patrons/226ea3a4617749548580ec2d9a263e24.jpg',
icon: 'https://assets.misskey-hub.net/patrons/226ea3a4617749548580ec2d9a263e24.jpg',
}, {
name: 'フランギ・シュウ',
icon: 'https://misskey-hub.net/patrons/3016d37e35f3430b90420176c912d304.jpg',
icon: 'https://assets.misskey-hub.net/patrons/3016d37e35f3430b90420176c912d304.jpg',
}, {
name: '百日紅',
icon: 'https://misskey-hub.net/patrons/302dce2898dd457ba03c3f7dc037900b.jpg',
icon: 'https://assets.misskey-hub.net/patrons/302dce2898dd457ba03c3f7dc037900b.jpg',
}, {
name: 'taichan',
icon: 'https://misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.png',
icon: 'https://assets.misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.png',
}];
const patrons = [

View File

@@ -74,15 +74,26 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>Enable</template>
</MkSwitch>
<MkSwitch v-model="enableVerifymailApi" @update:modelValue="save">
<template #label>Use Verifymail API</template>
<template #label>Use Verifymail.io API</template>
</MkSwitch>
<MkInput v-model="verifymailAuthKey" @update:modelValue="save">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>Verifymail API Auth Key</template>
<template #label>Verifymail.io API Auth Key</template>
</MkInput>
</div>
</MkFolder>
<MkFolder>
<template #label>Banned Email Domains</template>
<div class="_gaps_m">
<MkTextarea v-model="bannedEmailDomains">
<template #label>Banned Email Domains List</template>
</MkTextarea>
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
</div>
</MkFolder>
<MkFolder>
<template #label>Log IP address</template>
<template v-if="enableIpLogging" #suffix>Enabled</template>
@@ -124,6 +135,7 @@ import FormSuspense from '@/components/form/suspense.vue';
import MkRange from '@/components/MkRange.vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import * as os from '@/os.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
@@ -141,6 +153,7 @@ const enableIpLogging = ref<boolean>(false);
const enableActiveEmailValidation = ref<boolean>(false);
const enableVerifymailApi = ref<boolean>(false);
const verifymailAuthKey = ref<string | null>(null);
const bannedEmailDomains = ref<string>('');
async function init() {
const meta = await os.api('admin/meta');
@@ -161,6 +174,7 @@ async function init() {
enableActiveEmailValidation.value = meta.enableActiveEmailValidation;
enableVerifymailApi.value = meta.enableVerifymailApi;
verifymailAuthKey.value = meta.verifymailAuthKey;
bannedEmailDomains.value = meta.bannedEmailDomains.join('\n');
}
function save() {
@@ -180,6 +194,7 @@ function save() {
enableActiveEmailValidation: enableActiveEmailValidation.value,
enableVerifymailApi: enableVerifymailApi.value,
verifymailAuthKey: verifymailAuthKey.value,
bannedEmailDomains: bannedEmailDomains.value.split('\n'),
}).then(() => {
fetchInstance();
});

View File

@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
<MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ti ti-star"></i></MkButton>
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" :class="$style.banner">
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : undefined }" :class="$style.banner">
<div :class="$style.bannerStatus">
<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
<div><i class="ti ti-pencil ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFoldableSection>
<template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
<div v-if="channel.pinnedNotes.length > 0" class="_gaps">
<div v-if="channel.pinnedNotes && channel.pinnedNotes.length > 0" class="_gaps">
<MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/>
</div>
</MkFoldableSection>
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- スマホタブレットの場合キーボードが表示されると投稿が見づらくなるのでデスクトップ場合のみ自動でフォーカスを当てる -->
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after"/>
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
</div>
<div v-else-if="tab === 'featured'">
<MkNotes :pagination="featuredPagination"/>
@@ -69,6 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkPostForm from '@/components/MkPostForm.vue';
import MkTimeline from '@/components/MkTimeline.vue';
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
@@ -89,6 +90,7 @@ import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { PageHeaderItem } from '@/types/page-header.js';
import { isSupportShare } from '@/scripts/navigator.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { miLocalStorage } from '@/local-storage.js';
const router = useRouter();
@@ -97,7 +99,7 @@ const props = defineProps<{
}>();
const tab = ref('overview');
const channel = ref(null);
const channel = ref<Misskey.entities.Channel | null>(null);
const favorited = ref(false);
const searchQuery = ref('');
const searchPagination = ref();
@@ -114,14 +116,23 @@ watch(() => props.channelId, async () => {
channel.value = await os.api('channels/show', {
channelId: props.channelId,
});
favorited.value = channel.value.isFavorited;
favorited.value = channel.value.isFavorited ?? false;
if (favorited.value || channel.value.isFollowing) {
tab.value = 'timeline';
}
if ((favorited.value || channel.value.isFollowing) && channel.value.lastNotedAt) {
const lastReadedAt: number = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.value.id}`) ?? 0;
const lastNotedAt = Date.parse(channel.value.lastNotedAt);
if (lastNotedAt > lastReadedAt) {
miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.value.id}`, lastNotedAt);
}
}
}, { immediate: true });
function edit() {
router.push(`/channels/${channel.value.id}/edit`);
router.push(`/channels/${channel.value?.id}/edit`);
}
function openPostForm() {
@@ -131,6 +142,8 @@ function openPostForm() {
}
function favorite() {
if (!channel.value) return;
os.apiWithDialog('channels/favorite', {
channelId: channel.value.id,
}).then(() => {
@@ -139,6 +152,8 @@ function favorite() {
}
async function unfavorite() {
if (!channel.value) return;
const confirm = await os.confirm({
type: 'warning',
text: i18n.ts.unfavoriteConfirm,
@@ -152,6 +167,8 @@ async function unfavorite() {
}
async function search() {
if (!channel.value) return;
const query = searchQuery.value.toString().trim();
if (query == null) return;
@@ -176,6 +193,10 @@ const headerActions = computed(() => {
icon: 'ti ti-link',
text: i18n.ts.copyUrl,
handler: async (): Promise<void> => {
if (!channel.value) {
console.warn('failed to copy channel URL. channel.value is null.');
return;
}
copyToClipboard(`${url}/channels/${channel.value.id}`);
os.success();
},
@@ -186,9 +207,14 @@ const headerActions = computed(() => {
icon: 'ti ti-share',
text: i18n.ts.share,
handler: async (): Promise<void> => {
if (!channel.value) {
console.warn('failed to share channel. channel.value is null.');
return;
}
navigator.share({
title: channel.value.name,
text: channel.value.description,
text: channel.value.description ?? undefined,
url: `${url}/channels/${channel.value.id}`,
});
},

View File

@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
// SPECIFICATION: https://misskey-hub.net/docs/features/share-form.html
// SPECIFICATION: https://misskey-hub.net/docs/for-users/features/share-form/
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';

View File

@@ -48,6 +48,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { antennasCache, userListsCache } from '@/cache.js';
import { deviceKind } from '@/scripts/device-kind.js';
import { MenuItem } from '@/types/menu.js';
import { miLocalStorage } from '@/local-storage.js';
provide('shouldOmitHeaderTitle', true);
@@ -125,12 +126,17 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
limit: 100,
});
const items: MenuItem[] = [
...channels.map(channel => ({
type: 'link' as const,
text: channel.name,
indicate: channel.hasUnreadNote,
to: `/channels/${channel.id}`,
})),
...channels.map(channel => {
const lastReadedAt = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.id}`) ?? null;
const hasUnreadNote = (lastReadedAt && channel.lastNotedAt) ? Date.parse(channel.lastNotedAt) > lastReadedAt : !!(!lastReadedAt && channel.lastNotedAt);
return {
type: 'link' as const,
text: channel.name,
indicate: hasUnreadNote,
to: `/channels/${channel.id}`,
};
}),
(channels.length === 0 ? undefined : { type: 'divider' }),
{
type: 'link' as const,