This commit is contained in:
mattyatea 2024-02-11 20:23:39 +09:00
parent 0cf3082902
commit 399f7c451b
11 changed files with 691 additions and 613 deletions

4
locales/index.d.ts vendored
View File

@ -84,6 +84,10 @@ export interface Locale extends ILocale {
* *
*/ */
"ruby": string; "ruby": string;
/**
*
*/
"pinnedChannel": string;
/** /**
* *
*/ */

View File

@ -17,6 +17,7 @@ notificationIndicator: "通知のインジケーターの数字を表示する"
hanntenn: "アイコンとバナーを反転させる" hanntenn: "アイコンとバナーを反転させる"
hanntennInfo: "ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。" hanntennInfo: "ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。"
ruby: "ルビ" ruby: "ルビ"
pinnedChannel: "ピン留めされたチャンネル"
gotIt: "わかった" gotIt: "わかった"
cancel: "キャンセル" cancel: "キャンセル"
myLists: "自分の作成したリスト" myLists: "自分の作成したリスト"

View File

@ -12,3 +12,5 @@ export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/role
export const userListsCache = new Cache<Misskey.entities.UserList[]>(1000 * 60 * 30, () => misskeyApi('users/lists/list')); export const userListsCache = new Cache<Misskey.entities.UserList[]>(1000 * 60 * 30, () => misskeyApi('users/lists/list'));
export const antennasCache = new Cache<Misskey.entities.Antenna[]>(1000 * 60 * 30, () => misskeyApi('antennas/list')); export const antennasCache = new Cache<Misskey.entities.Antenna[]>(1000 * 60 * 30, () => misskeyApi('antennas/list'));
export const userFavoriteListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list-favorite')); export const userFavoriteListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list-favorite'));
export const userChannelsCache = new Cache<Misskey.entities.UserChannel[]>(1000 * 60 * 30, () => misskeyApi('channels/owned'));
export const userChannelFollowingsCache = new Cache<Misskey.entities.UserChannelFollowing[]>(1000 * 60 * 30, () => misskeyApi('channels/followed'));

View File

@ -4,13 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div <div
:class="[$style.root, { [$style.modal]: modal, _popup: modal }]" :class="[$style.root, { [$style.modal]: modal, _popup: modal }]"
@dragover.stop="onDragover" @dragover.stop="onDragover"
@dragenter="onDragenter" @dragenter="onDragenter"
@dragleave="onDragleave" @dragleave="onDragleave"
@drop.stop="onDrop" @drop.stop="onDrop"
> >
<header :class="$style.header"> <header :class="$style.header">
<div :class="$style.headerLeft"> <div :class="$style.headerLeft">
<button v-if="!fixed" :class="$style.cancel" class="_button" @click="cancel"><i class="ti ti-x"></i></button> <button v-if="!fixed" :class="$style.cancel" class="_button" @click="cancel"><i class="ti ti-x"></i></button>
@ -96,11 +96,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<datalist id="hashtags"> <datalist id="hashtags">
<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/> <option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>
</datalist> </datalist>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent , provide, shallowRef, ref, computed } from 'vue'; import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
@ -125,7 +125,7 @@ import {
iconDark, iconDark,
iconLight, iconLight,
notePostInterruptors, notePostInterruptors,
postFormActions postFormActions,
} from '@/store.js'; } from '@/store.js';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@ -146,7 +146,6 @@ const $i = signinRequired();
const modal = inject('modal'); const modal = inject('modal');
let gamingType = computed(defaultStore.makeGetterSetter('gamingType')); let gamingType = computed(defaultStore.makeGetterSetter('gamingType'));
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
reply?: Misskey.entities.Note; reply?: Misskey.entities.Note;
renote?: Misskey.entities.Note; renote?: Misskey.entities.Note;
@ -286,7 +285,6 @@ watch(text, () => {
}, { immediate: true }); }, { immediate: true });
watch(visibility, () => { watch(visibility, () => {
switch (visibility.value) { switch (visibility.value) {
case 'public': case 'public':
localOnly.value = props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly; localOnly.value = props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
@ -402,11 +400,11 @@ function checkMissingMention() {
for (const x of extractMentions(ast)) { for (const x of extractMentions(ast)) {
if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) { if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
hasNotSpecifiedMentions.value = true; hasNotSpecifiedMentions.value = true;
return;} return;
}
} }
} }
hasNotSpecifiedMentions.value = false; hasNotSpecifiedMentions.value = false;
} }
function addMissingMention() { function addMissingMention() {
@ -420,9 +418,11 @@ function addMissingMention() {
} }
} }
} }
function insertRuby() { function insertRuby() {
insertTextAtCursor(textareaEl.value, '$[ruby 本文 上につくやつ]'); insertTextAtCursor(textareaEl.value, '$[ruby 本文 上につくやつ]');
} }
function togglePoll() { function togglePoll() {
if (poll.value) { if (poll.value) {
poll.value = null; poll.value = null;
@ -458,7 +458,7 @@ function focus() {
} }
function chooseFileFrom(ev) { function chooseFileFrom(ev) {
if (props.mock) return;selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => { if (props.mock) return; selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => {
for (const file of files_) { for (const file of files_) {
files.value.push(file); files.value.push(file);
} }
@ -485,7 +485,7 @@ function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities
} }
function upload(file: File, name?: string): void { function upload(file: File, name?: string): void {
if (props.mock) return;uploadFile(file, defaultStore.state.uploadFolder, name).then(res => { if (props.mock) return; uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
files.value.push(res); files.value.push(res);
}); });
} }
@ -499,7 +499,7 @@ function setVisibility() {
os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), { os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), {
currentVisibility: visibility.value, currentVisibility: visibility.value,
isSilenced: $i.isSilenced,localOnly: localOnly.value, isSilenced: $i.isSilenced, localOnly: localOnly.value,
src: visibilityButton.value, src: visibilityButton.value,
}, { }, {
changeVisibility: v => { changeVisibility: v => {
@ -591,7 +591,7 @@ function removeVisibleUser(user) {
function clear() { function clear() {
text.value = ''; text.value = '';
schedule.value = null;files.value = []; schedule.value = null; files.value = [];
poll.value = null; poll.value = null;
quoteId.value = null; quoteId.value = null;
} }
@ -610,11 +610,11 @@ function onCompositionEnd(ev: CompositionEvent) {
} }
async function onPaste(ev: ClipboardEvent) { async function onPaste(ev: ClipboardEvent) {
if (props.mock) return;if (!ev.clipboardData) return; if (props.mock) return; if (!ev.clipboardData) return;
for (const { item, i } of Array.from(ev.clipboardData.items, (data, x) => ({ item: data, i: x }))) { for (const { item, i } of Array.from(ev.clipboardData.items, (data, x) => ({ item: data, i: x }))) {
if (item.kind === 'file') { if (item.kind === 'file') {
const file = item.getAsFile();if (!file) continue; const file = item.getAsFile(); if (!file) continue;
const lio = file.name.lastIndexOf('.'); const lio = file.name.lastIndexOf('.');
const ext = lio >= 0 ? file.name.slice(lio) : ''; const ext = lio >= 0 ? file.name.slice(lio) : '';
const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`;
@ -731,7 +731,7 @@ async function post(ev?: MouseEvent) {
text: i18n.ts.cwNotationRequired, text: i18n.ts.cwNotationRequired,
}); });
return; return;
}if (ev) { } if (ev) {
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null; const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
if (el) { if (el) {
@ -739,9 +739,10 @@ async function post(ev?: MouseEvent) {
const x = rect.left + (el.offsetWidth / 2); const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2); const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end'); os.popup(MkRippleEffect, { x, y }, {}, 'end');
}} }
}
if (props.mock) return;const annoying = if (props.mock) return; const annoying =
text.value.includes('$[x2') || text.value.includes('$[x2') ||
text.value.includes('$[x3') || text.value.includes('$[x3') ||
text.value.includes('$[x4') || text.value.includes('$[x4') ||
@ -819,7 +820,7 @@ async function post(ev?: MouseEvent) {
if (postAccount.value) { if (postAccount.value) {
const storedAccounts = await getAccounts(); const storedAccounts = await getAccounts();
token = storedAccounts.find(x => x.id === postAccount?.value?.id)?.token; token = storedAccounts.find(x => x.id === postAccount.value?.id)?.token;
} }
posting.value = true; posting.value = true;
@ -952,7 +953,7 @@ function showActions(ev: MouseEvent) {
const postAccount = ref<Misskey.entities.UserDetailed | null>(null); const postAccount = ref<Misskey.entities.UserDetailed | null>(null);
function openAccountMenu(ev: MouseEvent) { function openAccountMenu(ev: MouseEvent) {
if (props.mock) return;openAccountMenu_({ if (props.mock) return; openAccountMenu_({
withExtraOperation: false, withExtraOperation: false,
includeCurrentAccount: true, includeCurrentAccount: true,
active: postAccount.value != null ? postAccount.value.id : $i.id, active: postAccount.value != null ? postAccount.value.id : $i.id,
@ -968,7 +969,7 @@ function openAccountMenu(ev: MouseEvent) {
function openOtherSettingsMenu(ev: MouseEvent) { function openOtherSettingsMenu(ev: MouseEvent) {
let reactionAcceptanceIcon: string; let reactionAcceptanceIcon: string;
switch (reactionAcceptance) { switch (reactionAcceptance.value) {
case 'likeOnly': case 'likeOnly':
reactionAcceptanceIcon = 'ti ti-heart'; reactionAcceptanceIcon = 'ti ti-heart';
break; break;
@ -985,13 +986,13 @@ function openOtherSettingsMenu(ev: MouseEvent) {
text: i18n.ts.reactionAcceptance, text: i18n.ts.reactionAcceptance,
icon: reactionAcceptanceIcon, icon: reactionAcceptanceIcon,
action: toggleReactionAcceptance, action: toggleReactionAcceptance,
}, ($i.policies?.canScheduleNote) ? { }, ($i.policies.canScheduleNote) ? {
type: 'button', type: 'button',
text: i18n.ts.schedulePost, text: i18n.ts.schedulePost,
icon: 'ti ti-calendar-time', icon: 'ti ti-calendar-time',
indicate: (schedule != null), indicate: (schedule.value != null),
action: toggleSchedule, action: toggleSchedule,
} : undefined, ...(($i.policies?.canScheduleNote) ? [{ type: 'divider' }, { } : undefined, ...(($i.policies.canScheduleNote) ? [{ type: 'divider' }, {
type: 'button', type: 'button',
text: i18n.ts._schedulePost.list, text: i18n.ts._schedulePost.list,
icon: 'ti ti-calendar-event', icon: 'ti ti-calendar-event',
@ -1015,8 +1016,8 @@ onMounted(() => {
} }
// TODO: detach when unmount // TODO: detach when unmount
if (textareaEl.value)new Autocomplete(textareaEl.value, text); if (textareaEl.value) new Autocomplete(textareaEl.value, text);
if (cwInputEl.value)new Autocomplete(cwInputEl.value, cw); if (cwInputEl.value) new Autocomplete(cwInputEl.value, cw);
if (hashtagsInputEl.value) new Autocomplete(hashtagsInputEl.value, hashtags); if (hashtagsInputEl.value) new Autocomplete(hashtagsInputEl.value, hashtags);
nextTick(() => { nextTick(() => {
@ -1052,12 +1053,12 @@ onMounted(() => {
poll.value = { poll.value = {
choices: init.poll.choices.map(x => x.text), choices: init.poll.choices.map(x => x.text),
multiple: init.poll.multiple, multiple: init.poll.multiple,
expiresAt: init.poll.expiresAt? (new Date(init.poll.expiresAt)).getTime() : null, expiresAt: init.poll.expiresAt ? (new Date(init.poll.expiresAt)).getTime() : null,
expiredAfter: null, expiredAfter: null,
}; };
} }
visibility.value = init.visibility; visibility.value = init.visibility;
localOnly.value = init.localOnly?? false; localOnly.value = init.localOnly ?? false;
quoteId.value = init.renote ? init.renote.id : null; quoteId.value = init.renote ? init.renote.id : null;
} }

View File

@ -38,6 +38,7 @@ const props = withDefaults(defineProps<{
withRenotes?: boolean; withRenotes?: boolean;
withReplies?: boolean; withReplies?: boolean;
onlyFiles?: boolean; onlyFiles?: boolean;
}>(), { }>(), {
withRenotes: true, withRenotes: true,
withReplies: false, withReplies: false,

View File

@ -177,7 +177,6 @@ onUnmounted(() => {
margin-left: 0; margin-left: 0;
} }
> .titleContainer { > .titleContainer {
margin: 0 auto;
max-width: 100%; max-width: 100%;
} }
} }

View File

@ -38,7 +38,6 @@ 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'"/> <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" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/> <MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
</div> </div>
<div v-else-if="tab === 'featured'" key="featured"> <div v-else-if="tab === 'featured'" key="featured">

View File

@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import XNotifications from '@/components/MkNotifications.vue'; import XNotifications from '@/components/MkNotifications.vue';
import MkNotes from '@/components/MkNotes.vue'; import MkNotes from '@/components/MkNotes.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
@ -74,7 +74,7 @@ const headerActions = computed(() => [{
highlighted: includeTypes.value != null, highlighted: includeTypes.value != null,
handler: setFilter, handler: setFilter,
}].filter(x => x !== undefined)); }].filter(x => x !== undefined));
misskeyApi('notifications/mark-all-as-read');
const headerTabs = computed(() => [{ const headerTabs = computed(() => [{
key: 'all', key: 'all',
title: i18n.ts.all, title: i18n.ts.all,
@ -93,6 +93,9 @@ definePageMetadata(computed(() => ({
title: i18n.ts.notifications, title: i18n.ts.notifications,
icon: 'ti ti-bell', icon: 'ti ti-bell',
}))); })));
onMounted(() => {
misskeyApi('notifications/mark-all-as-read');
});
</script> </script>
<style module lang="scss"> <style module lang="scss">

View File

@ -45,10 +45,18 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-if="pinnedMax > defaultStore.reactiveState.pinnedUserLists.value.length " @click="setPinnedList()">{{ i18n.ts.add }}</MkButton> <MkButton v-if="pinnedMax > defaultStore.reactiveState.pinnedUserLists.value.length " @click="setPinnedList()">{{ i18n.ts.add }}</MkButton>
<MkButton v-if="defaultStore.reactiveState.pinnedUserLists.value.length " danger @click="removePinnedList('all')"><i class="ti ti-trash"></i> {{ i18n.ts.all }}{{ i18n.ts.remove }}</MkButton> <MkButton v-if="defaultStore.reactiveState.pinnedUserLists.value.length " danger @click="removePinnedList('all')"><i class="ti ti-trash"></i> {{ i18n.ts.all }}{{ i18n.ts.remove }}</MkButton>
</MkFolder> </MkFolder>
<MkFolder>
<template #label>{{ i18n.ts.pinnedChannel }}</template>
<div v-for="pinnedLists in defaultStore.reactiveState.pinnedChannels.value" class="_margin">
{{ pinnedLists.name }}
<MkButton danger @click="removePinnedChannel(pinnedLists.id,pinnedLists.name)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
</div>
<MkButton v-if="pinnedMax > defaultStore.reactiveState.pinnedChannels.value.length " @click="setPinnedChannel()">{{ i18n.ts.add }}</MkButton>
<MkButton v-if="defaultStore.reactiveState.pinnedChannels.value.length " danger @click="removePinnedChannel('all')"><i class="ti ti-trash"></i> {{ i18n.ts.all }}{{ i18n.ts.remove }}</MkButton>
</MkFolder>
<MkFoldableSection :expanded="false" class="item"> <MkFoldableSection :expanded="false" class="item">
<template #header>{{ i18n.ts.topbarCustom }}</template> <template #header>{{ i18n.ts.topbarCustom }}</template>
{{ i18n.ts._timelines.home }} {{ i18n.ts._timelines.home }}
<MkSwitch v-model="showHomeTimeline">{{ i18n.ts.enable }}</MkSwitch> <MkSwitch v-model="showHomeTimeline">{{ i18n.ts.enable }}</MkSwitch>
<br> <br>
@ -379,7 +387,7 @@ import { claimAchievement } from '@/scripts/achievements.js';
import MkColorInput from '@/components/MkColorInput.vue'; import MkColorInput from '@/components/MkColorInput.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import { userFavoriteListsCache, userListsCache } from '@/cache.js'; import { userChannelFollowingsCache, userChannelsCache, userFavoriteListsCache, userListsCache } from '@/cache.js';
const lang = ref(miLocalStorage.getItem('lang')); const lang = ref(miLocalStorage.getItem('lang'));
const fontSize = ref(miLocalStorage.getItem('fontSize')); const fontSize = ref(miLocalStorage.getItem('fontSize'));
@ -638,7 +646,7 @@ async function setPinnedList() {
} }
} }
async function removePinnedList(id, name) { async function removePinnedList(id, name?:string) {
if (!id) return; if (!id) return;
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: 'warning', type: 'warning',
@ -658,6 +666,46 @@ async function removePinnedList(id, name) {
defaultStore.set('pinnedUserLists', newPinnedLists); defaultStore.set('pinnedUserLists', newPinnedLists);
} }
async function setPinnedChannel() {
const myChannels = await userChannelsCache.fetch();
const favoriteChannels = await userChannelFollowingsCache.fetch();
let channels = [...new Set([...myChannels, ...favoriteChannels])];
const { canceled, result: channel } = await os.select({
title: i18n.ts.selectList,
items: channels.map(x => ({
value: x, text: x.name,
})),
});
if (canceled) return;
let pinnedChannels = defaultStore.state.pinnedChannels;
// Check if the id is already present in pinnedLists
if (!pinnedChannels.some(pinnedChannel => pinnedChannel.id === channel.id)) {
pinnedChannels.push(channel);
defaultStore.set('pinnedChannels', pinnedChannels);
}
}
async function removePinnedChannel(id, name?:string) {
if (!id) return;
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.removeAreYouSure({ x: name ?? id }),
});
if (canceled) return;
if (id === 'all') {
if (canceled) return;
defaultStore.set('pinnedChannels', []);
return;
}
const pinnedChannels = defaultStore.state.pinnedChannels;
const newPinnedChannels = pinnedChannels.filter(pinnedchannel => pinnedchannel.id !== id);
defaultStore.set('pinnedChannels', newPinnedChannels);
}
let smashCount = 0; let smashCount = 0;
let smashTimer: number | null = null; let smashTimer: number | null = null;

View File

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()"> <MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
{{ i18n.ts._timelineDescription[src] }} {{ i18n.ts._timelineDescription[src] }}
</MkInfo> </MkInfo>
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/> <MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostForm.value" :channel="channelInfo" :autofocus="deviceKind === 'desktop'" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
<div :class="$style.tl"> <div :class="$style.tl">
<MkTimeline <MkTimeline
@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:key="src + withRenotes + withReplies + onlyFiles" :key="src + withRenotes + withReplies + onlyFiles"
:src="src.split(':')[0]" :src="src.split(':')[0]"
:list="src.split(':')[1]" :list="src.split(':')[1]"
:channel="src.split(':')[1]"
:withRenotes="withRenotes" :withRenotes="withRenotes"
:withReplies="withReplies" :withReplies="withReplies"
:onlyFiles="onlyFiles" :onlyFiles="onlyFiles"
@ -112,8 +113,19 @@ const remoteLocalTimelineEnable4 = ref(defaultStore.state.remoteLocalTimelineEna
const remoteLocalTimelineEnable5 = ref(defaultStore.state.remoteLocalTimelineEnable5); const remoteLocalTimelineEnable5 = ref(defaultStore.state.remoteLocalTimelineEnable5);
const showHomeTimeline = ref(defaultStore.state.showHomeTimeline); const showHomeTimeline = ref(defaultStore.state.showHomeTimeline);
const showSocialTimeline = ref(defaultStore.state.showSocialTimeline); const showSocialTimeline = ref(defaultStore.state.showSocialTimeline);
watch(src, () => { const channelInfo = ref();
if (src.value.split(':')[0] === 'channel') {
const channelId = src.value.split(':')[1];
channelInfo.value = misskeyApi('channels/show', { channelId });
}
watch(src, async () => {
queue.value = 0; queue.value = 0;
if (src.value.split(':')[0] === 'channel') {
const channelId = src.value.split(':')[1];
channelInfo.value = misskeyApi('channels/show', { channelId });
} else {
channelInfo.value = null;
}
}); });
function queueUpdated(q: number): void { function queueUpdated(q: number): void {
@ -285,6 +297,11 @@ const headerTabs = computed(() => [...(defaultStore.reactiveState.pinnedUserList
title: l.name, title: l.name,
icon: 'ti ti-star', icon: 'ti ti-star',
iconOnly: defaultStore.state.topBarNameShown ?? false, iconOnly: defaultStore.state.topBarNameShown ?? false,
}))), ...(defaultStore.reactiveState.pinnedChannels.value.map(l => ({
key: 'channel:' + l.id,
title: l.name,
icon: 'ti ti-star',
iconOnly: defaultStore.state.topBarNameShown ?? false,
}))), ...(showHomeTimeline.value ? [{ }))), ...(showHomeTimeline.value ? [{
key: 'home', key: 'home',
title: i18n.ts._timelines.home, title: i18n.ts._timelines.home,

View File

@ -280,7 +280,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'deviceAccount', where: 'deviceAccount',
default: [] as Misskey.entities.UserList[], default: [] as Misskey.entities.UserList[],
}, },
pinnedChannels: {
where: 'deviceAccount',
default: [] as Misskey.entities.Channel[],
},
overridedDeviceKind: { overridedDeviceKind: {
where: 'device', where: 'device',
default: null as null | 'smartphone' | 'tablet' | 'desktop', default: null as null | 'smartphone' | 'tablet' | 'desktop',