Merge remote-tracking branch 'misskey-original/develop' into timeline-toka

# Conflicts:
#	locales/index.d.ts
#	locales/ja-JP.yml
#	package.json
#	packages/backend/src/server/api/endpoints/admin/emoji/add.ts
#	packages/backend/src/server/api/stream/channels/global-timeline.ts
#	packages/backend/src/server/api/stream/channels/home-timeline.ts
#	packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
#	packages/backend/src/server/api/stream/channels/local-timeline.ts
#	packages/frontend/src/components/MkNote.vue
#	packages/frontend/src/components/MkPostForm.vue
#	packages/frontend/src/pages/timeline.vue
#	pnpm-lock.yaml
This commit is contained in:
mattyatea 2023-10-07 04:15:59 +09:00
commit 560482315b
177 changed files with 3915 additions and 2343 deletions

View file

@ -186,7 +186,7 @@ import {deepClone} from '@/scripts/clone.js';
import {useTooltip} from '@/scripts/use-tooltip.js';
import {claimAchievement} from '@/scripts/achievements.js';
import {getNoteSummary} from '@/scripts/get-note-summary.js';
import {MenuItem} from '@/types/menu';
import {MenuItem} from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import {showMovedDialog} from '@/scripts/show-moved-dialog.js';
import {shouldCollapsed} from '@/scripts/collapsed.js';
@ -232,11 +232,11 @@ const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : n
const isLong = shouldCollapsed(appearNote);
const collapsed = ref(appearNote.cw == null && isLong);
const isDeleted = ref(false);
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
const translation = ref<any>(null);
const translating = ref(false);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i.id));
let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null)));
const keymap = {
@ -519,7 +519,6 @@ function focusAfter() {
focusNext(el.value);
}
function readPromo() {
os.api('promo/read', {
noteId: appearNote.id,
@ -529,7 +528,6 @@ function readPromo() {
</script>
<style lang="scss" module>
.root {
position: relative;
transition: box-shadow 0.1s ease;
@ -557,7 +555,7 @@ function readPromo() {
//
// ()
//content-visibility: auto;
//contain-intrinsic-size: 0 128px;
//contain-intrinsic-size: 0 128px;
&:focus-visible {
outline: none;

View file

@ -93,9 +93,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<footer>
<div :class="$style.noteFooterInfo">
<div v-if="appearNote.updatedAt">
{{ i18n.ts.edited }}: <MkTime :time="appearNote.updatedAt" mode="detail"/>
</div>
<MkA :to="notePage(appearNote)">
<MkTime :time="appearNote.createdAt" mode="detail"/>
</MkA>
@ -214,7 +211,7 @@ import { useNoteCapture } from '@/scripts/use-note-capture.js';
import { deepClone } from '@/scripts/clone.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { MenuItem } from '@/types/menu';
import { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
@ -291,7 +288,7 @@ let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note
const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
const translation = ref(null);
const translating = ref(false);
const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;

View file

@ -14,7 +14,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
</div>
<div :class="$style.info">
<span v-if="note.updatedAt" style="margin-right: 0.5em;" :title="i18n.ts.edited"><i class="ti ti-pencil"></i></span>
<MkA :to="notePage(note)">
<MkTime :time="note.createdAt"/>
</MkA>

View file

@ -49,9 +49,9 @@ import { notePage } from '@/filters/note.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import { userPage } from "@/filters/user";
import { checkWordMute } from "@/scripts/check-word-mute";
import { defaultStore } from "@/store";
import { userPage } from '@/filters/user.js';
import { checkWordMute } from '@/scripts/check-word-mute.js';
import { defaultStore } from '@/store.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@ -63,7 +63,7 @@ const props = withDefaults(defineProps<{
depth: 1,
});
const muted = ref(checkWordMute(props.note, $i, defaultStore.state.mutedWords));
const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
let showContent = $ref(false);
let replies: Misskey.entities.Note[] = $ref([]);

View file

@ -174,7 +174,7 @@ const props = withDefaults(defineProps<{
fixed?: boolean;
autofocus?: boolean;
freezeAfterPosted?: boolean;
updateMode?: boolean;
}>(), {
initialVisibleUsers: () => [],
autofocus: true,
@ -741,7 +741,7 @@ async function post(ev?: MouseEvent) {
visibility: visibility,
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
reactionAcceptance,
noteId: props.updateMode ? props.initialNote?.id : undefined,
};
if (withHashtags && hashtags && hashtags.trim() !== '') {
@ -764,7 +764,7 @@ async function post(ev?: MouseEvent) {
}
posting = true;
os.api(props.updateMode ? 'notes/update' : 'notes/create', postData, token).then(() => {
os.api('notes/create', postData, token).then(() => {
if (props.freezeAfterPosted) {
posted = true;
} else {

View file

@ -30,7 +30,6 @@ const props = defineProps<{
instant?: boolean;
fixed?: boolean;
autofocus?: boolean;
updateMode?: boolean;
}>();
const emit = defineEmits<{

View file

@ -23,11 +23,9 @@ const props = withDefaults(defineProps<{
role?: string;
sound?: boolean;
withRenotes?: boolean;
withReplies?: boolean;
onlyFiles?: boolean;
}>(), {
withRenotes: true,
withReplies: false,
onlyFiles: false,
});
@ -70,12 +68,10 @@ if (props.src === 'antenna') {
endpoint = 'notes/timeline';
query = {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
};
connection = stream.useChannel('homeTimeline', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
});
connection.on('note', prepend);
@ -85,12 +81,10 @@ if (props.src === 'antenna') {
endpoint = 'notes/local-timeline';
query = {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
};
connection = stream.useChannel('localTimeline', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
});
connection.on('note', prepend);
@ -111,12 +105,10 @@ if (props.src === 'antenna') {
endpoint = 'notes/hybrid-timeline';
query = {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
};
connection = stream.useChannel('hybridTimeline', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
});
connection.on('note', prepend);
@ -135,12 +127,10 @@ if (props.src === 'antenna') {
endpoint = 'notes/global-timeline';
query = {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
};
connection = stream.useChannel('globalTimeline', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
});
connection.on('note', prepend);
@ -164,13 +154,11 @@ if (props.src === 'antenna') {
endpoint = 'notes/user-list-timeline';
query = {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
listId: props.list,
};
connection = stream.useChannel('userList', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
listId: props.list,
});

View file

@ -61,7 +61,6 @@ export const ROLE_POLICIES = [
'gtlAvailable',
'ltlAvailable',
'canPublicNote',
'canEditNote',
'canInvite',
'inviteLimit',
'inviteLimitCycle',

View file

@ -292,6 +292,7 @@ const patrons = [
'kino3277',
'美少女JKぐーちゃん',
'てば',
'たっくん',
];
let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));

View file

@ -0,0 +1,81 @@
<!--
SPDX-FileCopyrightText: syuilo and other misskey contributors
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkStickyContainer>
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
<FormSuspense :p="init">
<FormSection>
<template #label>DeepL Translation</template>
<div class="_gaps_m">
<MkInput v-model="deeplAuthKey">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>DeepL Auth Key</template>
</MkInput>
<MkSwitch v-model="deeplIsPro">
<template #label>Pro account</template>
</MkSwitch>
</div>
</FormSection>
</FormSuspense>
</MkSpacer>
<template #footer>
<div :class="$style.footer">
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
</MkSpacer>
</div>
</template>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XHeader from './_header_.vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSection from '@/components/form/section.vue';
import * as os from '@/os.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
let deeplAuthKey: string = $ref('');
let deeplIsPro: boolean = $ref(false);
async function init() {
const meta = await os.api('admin/meta');
deeplAuthKey = meta.deeplAuthKey;
deeplIsPro = meta.deeplIsPro;
}
function save() {
os.apiWithDialog('admin/update-meta', {
deeplAuthKey,
deeplIsPro,
}).then(() => {
fetchInstance();
});
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.externalServices,
icon: 'ti ti-link',
});
</script>
<style lang="scss" module>
.footer {
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
}
</style>

View file

@ -212,6 +212,11 @@ const menuDef = $computed(() => [{
text: i18n.ts.proxyAccount,
to: '/admin/proxy-account',
active: currentPage?.route.name === 'proxy-account',
}, {
icon: 'ti ti-link',
text: i18n.ts.externalServices,
to: '/admin/external-services',
active: currentPage?.route.name === 'external-services',
}, {
icon: 'ti ti-adjustments',
text: i18n.ts.other,

View file

@ -29,8 +29,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'unmarkSensitiveDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
<span v-else-if="log.type === 'suspendRemoteInstance'">: {{ log.info.host }}</span>
<span v-else-if="log.type === 'unsuspendRemoteInstance'">: {{ log.info.host }}</span>
<span v-else-if="log.type === 'createGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
<span v-else-if="log.type === 'updateGlobalAnnouncement'">: {{ log.info.before.title }}</span>
<span v-else-if="log.type === 'deleteGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
<span v-else-if="log.type === 'createUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'updateUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
</template>
@ -88,6 +92,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<template v-else-if="log.type === 'updateGlobalAnnouncement'">
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<template v-else-if="log.type === 'updateUserAnnouncement'">
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<details>
<summary>raw</summary>

View file

@ -160,26 +160,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canEditNote, 'canEditNote'])">
<template #label>{{ i18n.ts._role._options.canEditNote }}</template>
<template #suffix>
<span v-if="role.policies.canEditNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ role.policies.canEditNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canEditNote)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="role.policies.canEditNote.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
</MkSwitch>
<MkSwitch v-model="role.policies.canEditNote.value" :disabled="role.policies.canEditNote.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
<MkRange v-model="role.policies.canEditNote.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
<template #label>{{ i18n.ts._role.priority }}</template>
</MkRange>
</div>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
<template #suffix>

View file

@ -48,14 +48,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canEditNote, 'canEditNote'])">
<template #label>{{ i18n.ts._role._options.canEditNote }}</template>
<template #suffix>{{ policies.canEditNote ? i18n.ts.yes : i18n.ts.no }}</template>
<MkSwitch v-model="policies.canEditNote">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
<template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template>

View file

@ -81,16 +81,24 @@ SPDX-License-Identifier: AGPL-3.0-only
</FormSection>
<FormSection>
<template #label>DeepL Translation</template>
<template #label>Timeline caching</template>
<div class="_gaps_m">
<MkInput v-model="deeplAuthKey">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>DeepL Auth Key</template>
<MkInput v-model="perLocalUserUserTimelineCacheMax" type="number">
<template #label>perLocalUserUserTimelineCacheMax</template>
</MkInput>
<MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number">
<template #label>perRemoteUserUserTimelineCacheMax</template>
</MkInput>
<MkInput v-model="perUserHomeTimelineCacheMax" type="number">
<template #label>perUserHomeTimelineCacheMax</template>
</MkInput>
<MkInput v-model="perUserListTimelineCacheMax" type="number">
<template #label>perUserListTimelineCacheMax</template>
</MkInput>
<MkSwitch v-model="deeplIsPro">
<template #label>Pro account</template>
</MkSwitch>
</div>
</FormSection>
</div>
@ -133,8 +141,10 @@ let cacheRemoteSensitiveFiles: boolean = $ref(false);
let enableServiceWorker: boolean = $ref(false);
let swPublicKey: any = $ref(null);
let swPrivateKey: any = $ref(null);
let deeplAuthKey: string = $ref('');
let deeplIsPro: boolean = $ref(false);
let perLocalUserUserTimelineCacheMax: number = $ref(0);
let perRemoteUserUserTimelineCacheMax: number = $ref(0);
let perUserHomeTimelineCacheMax: number = $ref(0);
let perUserListTimelineCacheMax: number = $ref(0);
async function init(): Promise<void> {
const meta = await os.api('admin/meta');
@ -149,8 +159,10 @@ async function init(): Promise<void> {
enableServiceWorker = meta.enableServiceWorker;
swPublicKey = meta.swPublickey;
swPrivateKey = meta.swPrivateKey;
deeplAuthKey = meta.deeplAuthKey;
deeplIsPro = meta.deeplIsPro;
perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax;
perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax;
perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax;
perUserListTimelineCacheMax = meta.perUserListTimelineCacheMax;
}
function save(): void {
@ -166,8 +178,10 @@ function save(): void {
enableServiceWorker,
swPublicKey,
swPrivateKey,
deeplAuthKey,
deeplIsPro,
perLocalUserUserTimelineCacheMax,
perRemoteUserUserTimelineCacheMax,
perUserHomeTimelineCacheMax,
perUserListTimelineCacheMax,
}).then(() => {
fetchInstance();
});

View file

@ -102,7 +102,6 @@ let searchKey = $ref('');
const featuredPagination = $computed(() => ({
endpoint: 'notes/featured' as const,
limit: 10,
offsetMode: true,
params: {
channelId: props.channelId,
},

View file

@ -22,7 +22,6 @@ import { i18n } from '@/i18n.js';
const paginationForNotes = {
endpoint: 'notes/featured' as const,
limit: 10,
offsetMode: true,
};
const paginationForPolls = {

View file

@ -29,16 +29,22 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
<div v-for="user in users" :key="user.id" :class="$style.userItem">
<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
<MkUserCardMini :user="user"/>
</MkA>
<button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
</div>
<MkButton v-if="!fetching && queueUserIds.length !== 0" v-appear="enableInfiniteScroll ? fetchMoreUsers : null" :class="$style.more" :style="{ cursor: 'pointer' }" primary rounded @click="fetchMoreUsers">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-if="fetching" class="loading"/>
<MkPagination ref="paginationEl" :pagination="membershipsPagination">
<template #default="{ items }">
<div class="_gaps_s">
<div v-for="item in items" :key="item.id">
<div :class="$style.userItem">
<MkA :class="$style.userItemBody" :to="`${userPage(item.user)}`">
<MkUserCardMini :user="item.user"/>
</MkA>
<button class="_button" :class="$style.menu" @click="showMembershipMenu(item, $event)"><i class="ti ti-dots"></i></button>
<button class="_button" :class="$style.remove" @click="removeUser(item, $event)"><i class="ti ti-x"></i></button>
</div>
</div>
</div>
</template>
</MkPagination>
</div>
</MkFolder>
</div>
@ -59,9 +65,11 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkInput from '@/components/MkInput.vue';
import { userListsCache } from '@/cache';
import { userListsCache } from '@/cache.js';
import { $i } from '@/account.js';
import { defaultStore } from '@/store.js';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
const {
enableInfiniteScroll,
} = defaultStore.reactiveState;
@ -70,40 +78,25 @@ const props = defineProps<{
listId: string;
}>();
const FETCH_USERS_LIMIT = 20;
const paginationEl = ref<InstanceType<typeof MkPagination>>();
let list = $ref<Misskey.entities.UserList | null>(null);
let users = $ref<Misskey.entities.UserLite[]>([]);
let queueUserIds = $ref<string[]>([]);
let fetching = $ref(true);
const isPublic = ref(false);
const name = ref('');
const membershipsPagination = {
endpoint: 'users/lists/get-memberships' as const,
limit: 30,
params: computed(() => ({
listId: props.listId,
})),
};
function fetchList() {
fetching = true;
os.api('users/lists/show', {
listId: props.listId,
}).then(_list => {
list = _list;
name.value = list.name;
isPublic.value = list.isPublic;
queueUserIds = list.userIds;
return fetchMoreUsers();
});
}
function fetchMoreUsers() {
if (!list) return;
if (fetching && users.length !== 0) return; // fetchingtrueusers
fetching = true;
os.api('users/show', {
userIds: queueUserIds.slice(0, FETCH_USERS_LIMIT),
}).then(_users => {
users = users.concat(_users);
queueUserIds = queueUserIds.slice(FETCH_USERS_LIMIT);
}).finally(() => {
fetching = false;
});
}
@ -114,12 +107,12 @@ function addUser() {
listId: list.id,
userId: user.id,
}).then(() => {
users.push(user);
paginationEl.value.reload();
});
});
}
async function removeUser(user, ev) {
async function removeUser(item, ev) {
os.popupMenu([{
text: i18n.ts.remove,
icon: 'ti ti-x',
@ -128,9 +121,28 @@ async function removeUser(user, ev) {
if (!list) return;
os.api('users/lists/pull', {
listId: list.id,
userId: user.id,
userId: item.userId,
}).then(() => {
users = users.filter(x => x.id !== user.id);
paginationEl.value.removeItem(item.id);
});
},
}], ev.currentTarget ?? ev.target);
}
async function showMembershipMenu(item, ev) {
os.popupMenu([{
text: item.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline,
icon: item.withReplies ? 'ti ti-messages-off' : 'ti ti-messages',
action: async () => {
os.api('users/lists/update-membership', {
listId: list.id,
userId: item.userId,
withReplies: !item.withReplies,
}).then(() => {
paginationEl.value.updateItem(item.id, (old) => ({
...old,
withReplies: !item.withReplies,
}));
});
},
}], ev.currentTarget ?? ev.target);
@ -202,6 +214,12 @@ definePageMetadata(computed(() => list ? {
align-self: center;
}
.menu {
width: 32px;
height: 32px;
align-self: center;
}
.more {
margin-left: auto;
margin-right: auto;

View file

@ -83,6 +83,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #value><code class="_monospace">{{ code }}</code></template>
</MkKeyValue>
</div>
<MkButton primary rounded gradate @click="downloadBackupCodes"><i class="ti ti-download"></i> {{ i18n.ts.download }}</MkButton>
</div>
</MkFolder>
</div>
@ -108,6 +110,7 @@ import * as os from '@/os.js';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
import { confetti } from '@/scripts/confetti.js';
import { $i } from '@/account.js';
defineProps<{
twoFactorData: {
@ -143,6 +146,16 @@ async function tokenDone() {
});
}
function downloadBackupCodes() {
if (backupCodes.value !== undefined) {
const txtBlob = new Blob([backupCodes.value.join('\n')], { type: 'text/plain' });
const dummya = document.createElement('a');
dummya.href = URL.createObjectURL(txtBlob);
dummya.download = `${$i?.username}-2fa-backup-codes.txt`;
dummya.click();
}
}
function allDone() {
dialog.value.close();
}

View file

@ -139,21 +139,11 @@ const menuDef = computed(() => [{
text: i18n.ts.roles,
to: '/settings/roles',
active: currentPage?.route.name === 'roles',
}, {
icon: 'ti ti-planet-off',
text: i18n.ts.instanceMute,
to: '/settings/instance-mute',
active: currentPage?.route.name === 'instance-mute',
}, {
icon: 'ti ti-ban',
text: i18n.ts.muteAndBlock,
to: '/settings/mute-block',
active: currentPage?.route.name === 'mute-block',
}, {
icon: 'ti ti-message-off',
text: i18n.ts.wordMute,
to: '/settings/word-mute',
active: currentPage?.route.name === 'word-mute',
}, {
icon: 'ti ti-api',
text: 'API',

View file

@ -22,7 +22,6 @@ import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const instanceMutes = ref($i!.mutedInstances.join('\n'));
const changed = ref(false);
@ -46,13 +45,4 @@ async function save() {
watch(instanceMutes, () => {
changed.value = true;
});
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.instanceMute,
icon: 'ti ti-planet-off',
});
</script>

View file

@ -5,13 +5,24 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps_m">
<MkTab v-model="tab">
<option value="renoteMute">{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</option>
<option value="mute">{{ i18n.ts.mutedUsers }}</option>
<option value="block">{{ i18n.ts.blockedUsers }}</option>
</MkTab>
<MkFolder>
<template #icon><i class="ti ti-message-off"></i></template>
<template #label>{{ i18n.ts.wordMute }}</template>
<XWordMute/>
</MkFolder>
<MkFolder>
<template #icon><i class="ti ti-planet-off"></i></template>
<template #label>{{ i18n.ts.instanceMute }}</template>
<XInstanceMute/>
</MkFolder>
<MkFolder>
<template #icon><i class="ti ti-repeat-off"></i></template>
<template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template>
<div v-if="tab === 'renoteMute'">
<MkPagination :pagination="renoteMutingPagination">
<template #empty>
<div class="_fullinfo">
@ -37,9 +48,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
</MkPagination>
</div>
</MkFolder>
<MkFolder>
<template #icon><i class="ti ti-eye-off"></i></template>
<template #label>{{ i18n.ts.mutedUsers }}</template>
<div v-else-if="tab === 'mute'">
<MkPagination :pagination="mutingPagination">
<template #empty>
<div class="_fullinfo">
@ -67,9 +81,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
</MkPagination>
</div>
</MkFolder>
<MkFolder>
<template #icon><i class="ti ti-ban"></i></template>
<template #label>{{ i18n.ts.blockedUsers }}</template>
<div v-else-if="tab === 'block'">
<MkPagination :pagination="blockingPagination">
<template #empty>
<div class="_fullinfo">
@ -97,24 +114,22 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
</MkPagination>
</div>
</MkFolder>
</div>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XInstanceMute from './mute-block.instance-mute.vue';
import XWordMute from './mute-block.word-mute.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkTab from '@/components/MkTab.vue';
import FormInfo from '@/components/MkInfo.vue';
import FormLink from '@/components/form/link.vue';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import * as os from '@/os.js';
import { infoImageUrl } from '@/instance.js';
let tab = $ref('renoteMute');
import MkFolder from '@/components/MkFolder.vue';
const renoteMutingPagination = {
endpoint: 'renote-mute/list' as const,

View file

@ -5,29 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps_m">
<MkTab v-model="tab">
<option value="soft">{{ i18n.ts._wordMute.soft }}</option>
<option value="hard">{{ i18n.ts._wordMute.hard }}</option>
</MkTab>
<div>
<div v-show="tab === 'soft'" class="_gaps_m">
<MkInfo>{{ i18n.ts._wordMute.softDescription }}</MkInfo>
<MkTextarea v-model="softMutedWords">
<span>{{ i18n.ts._wordMute.muteWords }}</span>
<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
</MkTextarea>
</div>
<div v-show="tab === 'hard'" class="_gaps_m">
<MkInfo>{{ i18n.ts._wordMute.hardDescription }} {{ i18n.ts.reflectMayTakeTime }}</MkInfo>
<MkTextarea v-model="hardMutedWords">
<span>{{ i18n.ts._wordMute.muteWords }}</span>
<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
</MkTextarea>
<MkKeyValue v-if="hardWordMutedNotesCount != null">
<template #key>{{ i18n.ts._wordMute.mutedNotes }}</template>
<template #value>{{ number(hardWordMutedNotesCount) }}</template>
</MkKeyValue>
</div>
<MkTextarea v-model="mutedWords">
<span>{{ i18n.ts._wordMute.muteWords }}</span>
<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
</MkTextarea>
</div>
<MkButton primary inline :disabled="!changed" @click="save()"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
</div>
@ -56,25 +38,15 @@ const render = (mutedWords) => mutedWords.map(x => {
}).join('\n');
const tab = ref('soft');
const softMutedWords = ref(render(defaultStore.state.mutedWords));
const hardMutedWords = ref(render($i!.mutedWords));
const hardWordMutedNotesCount = ref(null);
const mutedWords = ref(render($i!.mutedWords));
const changed = ref(false);
os.api('i/get-word-muted-notes-count', {}).then(response => {
hardWordMutedNotesCount.value = response?.count;
});
watch(softMutedWords, () => {
changed.value = true;
});
watch(hardMutedWords, () => {
watch(mutedWords, () => {
changed.value = true;
});
async function save() {
const parseMutes = (mutes, tab) => {
const parseMutes = (mutes) => {
// split into lines, remove empty lines and unnecessary whitespace
let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== '');
@ -92,7 +64,7 @@ async function save() {
os.alert({
type: 'error',
title: i18n.ts.regexpError,
text: i18n.t('regexpErrorDescription', { tab, line: i + 1 }) + '\n' + err.toString(),
text: i18n.t('regexpErrorDescription', { tab: 'word mute', line: i + 1 }) + '\n' + err.toString(),
});
// re-throw error so these invalid settings are not saved
throw err;
@ -105,29 +77,18 @@ async function save() {
return lines;
};
let softMutes, hardMutes;
let parsed;
try {
softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft);
hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard);
parsed = parseMutes(mutedWords.value);
} catch (err) {
// already displayed error message in parseMutes
return;
}
defaultStore.set('mutedWords', softMutes);
await os.api('i/update', {
mutedWords: hardMutes,
mutedWords: parsed,
});
changed.value = false;
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.wordMute,
icon: 'ti ti-message-off',
});
</script>

View file

@ -38,14 +38,12 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
const masterVolume = computed(soundConfigStore.makeGetterSetter('sound_masterVolume'));
const soundsKeys = ['note', 'noteMy', 'notification', 'chat', 'chatBg', 'antenna', 'channel'] as const;
const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel'] as const;
const sounds = ref<Record<typeof soundsKeys[number], Ref<any>>>({
note: soundConfigStore.reactiveState.sound_note,
noteMy: soundConfigStore.reactiveState.sound_noteMy,
notification: soundConfigStore.reactiveState.sound_notification,
chat: soundConfigStore.reactiveState.sound_chat,
chatBg: soundConfigStore.reactiveState.sound_chatBg,
antenna: soundConfigStore.reactiveState.sound_antenna,
channel: soundConfigStore.reactiveState.sound_channel,
});

View file

@ -15,11 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.tl">
<MkTimeline
ref="tlComponent"
:key="src + withRenotes + withReplies + onlyFiles"
:key="src + withRenotes + onlyFiles"
:src="src.split(':')[0]"
:list="src.split(':')[1]"
:withRenotes="withRenotes"
:withReplies="withReplies"
:onlyFiles="onlyFiles"
:sound="true"
@queue="queueUpdated"

View file

@ -129,14 +129,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
<template v-if="narrow">
<XPhotos :key="user.id" :user="user"/>
<XFiles :key="user.id" :user="user"/>
<XActivity :key="user.id" :user="user"/>
</template>
<MkNotes v-if="!disableNotes" :class="$style.tl" :noGap="true" :pagination="pagination"/>
<div v-if="!disableNotes">
<div style="margin-bottom: 8px;">{{ i18n.ts.featured }}</div>
<MkNotes :class="$style.tl" :noGap="true" :pagination="pagination"/>
</div>
</div>
</div>
<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
<XPhotos :key="user.id" :user="user"/>
<XFiles :key="user.id" :user="user"/>
<XActivity :key="user.id" :user="user"/>
</div>
</div>
@ -185,7 +188,7 @@ function calcAge(birthdate: string): number {
return yearDiff;
}
const XPhotos = defineAsyncComponent(() => import('./index.photos.vue'));
const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
const props = withDefaults(defineProps<{
@ -213,7 +216,7 @@ watch($$(moderationNote), async () => {
});
const pagination = {
endpoint: 'users/notes' as const,
endpoint: 'users/featured-notes' as const,
limit: 10,
params: computed(() => ({
userId: props.user.id,

View file

@ -6,20 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkContainer :max-height="300" :foldable="true">
<template #icon><i class="ti ti-photo"></i></template>
<template #header>{{ i18n.ts.images }}</template>
<template #header>{{ i18n.ts.files }}</template>
<div :class="$style.root">
<MkLoading v-if="fetching"/>
<div v-if="!fetching && images.length > 0" :class="$style.stream">
<div v-if="!fetching && files.length > 0" :class="$style.stream">
<MkA
v-for="image in images"
:key="image.note.id + image.file.id"
v-for="file in files"
:key="file.note.id + file.file.id"
:class="$style.img"
:to="notePage(image.note)"
:to="notePage(file.note)"
>
<ImgWithBlurhash :hash="image.file.blurhash" :src="thumbnail(image.file)" :title="image.file.name"/>
<!-- TODO: 画像以外のファイルに対応 -->
<ImgWithBlurhash :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name"/>
</MkA>
</div>
<p v-if="!fetching && images.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
<p v-if="!fetching && files.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
</div>
</MkContainer>
</template>
@ -40,7 +41,7 @@ const props = defineProps<{
}>();
let fetching = $ref(true);
let images = $ref<{
let files = $ref<{
note: Misskey.entities.Note;
file: Misskey.entities.DriveFile;
}[]>([]);
@ -52,24 +53,15 @@ function thumbnail(image: Misskey.entities.DriveFile): string {
}
onMounted(() => {
const image = [
'image/jpeg',
'image/webp',
'image/avif',
'image/png',
'image/gif',
'image/apng',
'image/vnd.mozilla.apng',
];
os.api('users/notes', {
userId: props.user.id,
fileType: image,
withFiles: true,
excludeNsfw: defaultStore.state.nsfw !== 'ignore',
limit: 10,
limit: 15,
}).then(notes => {
for (const note of notes) {
for (const file of note.files) {
images.push({
files.push({
note,
file,
});

View file

@ -29,7 +29,7 @@ const props = defineProps<{
user: Misskey.entities.UserDetailed;
}>();
const include = ref<string | null>(null);
const include = ref<string | null>('all');
const pagination = {
endpoint: 'users/notes' as const,
@ -38,6 +38,7 @@ const pagination = {
userId: props.user.id,
withRenotes: include.value === 'all',
withReplies: include.value === 'all' || include.value === 'files',
withChannelNotes: include.value === 'all',
withFiles: include.value === 'files',
})),
};

View file

@ -126,18 +126,10 @@ export const routes = [{
path: '/import-export',
name: 'import-export',
component: page(() => import('./pages/settings/import-export.vue')),
}, {
path: '/instance-mute',
name: 'instance-mute',
component: page(() => import('./pages/settings/instance-mute.vue')),
}, {
path: '/mute-block',
name: 'mute-block',
component: page(() => import('./pages/settings/mute-block.vue')),
}, {
path: '/word-mute',
name: 'word-mute',
component: page(() => import('./pages/settings/word-mute.vue')),
}, {
path: '/api',
name: 'api',
@ -435,6 +427,10 @@ export const routes = [{
path: '/proxy-account',
name: 'proxy-account',
component: page(() => import('./pages/admin/proxy-account.vue')),
}, {
path: '/external-services',
name: 'external-services',
component: page(() => import('./pages/admin/external-services.vue')),
}, {
path: '/other-settings',
name: 'other-settings',

View file

@ -172,10 +172,6 @@ export function getNoteMenu(props: {
});
}
function edit(): void {
os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel, updateMode: true });
}
function toggleFavorite(favorite: boolean): void {
claimAchievement('noteFavorited1');
os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
@ -356,11 +352,6 @@ export function getNoteMenu(props: {
),
...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
null,
appearNote.userId === $i.id && $i.policies.canEditNote ? {
icon: 'ti ti-edit',
text: i18n.ts.edit,
action: edit,
} : undefined,
appearNote.userId === $i.id ? {
icon: 'ti ti-edit',
text: i18n.ts.deleteAndEdit,

View file

@ -80,6 +80,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
});
}
async function toggleWithReplies() {
os.apiWithDialog('following/update', {
userId: user.id,
withReplies: !user.withReplies,
}).then(() => {
user.withReplies = !user.withReplies;
});
}
async function toggleNotify() {
os.apiWithDialog('following/update', {
userId: user.id,
@ -282,6 +291,10 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
// フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため
//if (user.isFollowing) {
menu = menu.concat([{
icon: user.withReplies ? 'ti ti-messages-off' : 'ti ti-messages',
text: user.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline,
action: toggleWithReplies,
}, {
icon: user.notify === 'none' ? 'ti ti-bell' : 'ti ti-bell-off',
text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes,
action: toggleNotify,

View file

@ -27,14 +27,6 @@ export const soundConfigStore = markRaw(new Storage('sound', {
where: 'account',
default: { type: 'syuilo/n-ea', volume: 1 },
},
sound_chat: {
where: 'account',
default: { type: 'syuilo/pope1', volume: 1 },
},
sound_chatBg: {
where: 'account',
default: { type: 'syuilo/waon', volume: 1 },
},
sound_antenna: {
where: 'account',
default: { type: 'syuilo/triple', volume: 1 },

View file

@ -71,13 +71,6 @@ export function useNoteCapture(props: {
break;
}
case 'updated': {
note.value.updatedAt = new Date().toISOString();
note.value.cw = body.cw;
note.value.text = body.text;
break;
}
case 'deleted': {
props.isDeletedRef.value = true;
break;

View file

@ -21,6 +21,8 @@ export function useTooltip(
let changeShowingState: (() => void) | null;
let autoHidingTimer;
const open = () => {
close();
if (!isHovering) return;
@ -33,6 +35,16 @@ export function useTooltip(
changeShowingState = () => {
showing.value = false;
};
autoHidingTimer = window.setInterval(() => {
if (!document.body.contains(elRef.value)) {
if (!isHovering) return;
isHovering = false;
window.clearTimeout(timeoutId);
close();
window.clearInterval(autoHidingTimer);
}
}, 1000);
};
const close = () => {
@ -53,6 +65,7 @@ export function useTooltip(
if (!isHovering) return;
isHovering = false;
window.clearTimeout(timeoutId);
window.clearInterval(autoHidingTimer);
close();
};
@ -67,6 +80,7 @@ export function useTooltip(
if (!isHovering) return;
isHovering = false;
window.clearTimeout(timeoutId);
window.clearInterval(autoHidingTimer);
close();
};

View file

@ -5,7 +5,7 @@
import { markRaw, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { miLocalStorage } from './local-storage';
import { miLocalStorage } from './local-storage.js';
import { Storage } from '@/pizzax.js';
interface PostFormAction {
@ -107,10 +107,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
},
mutedWords: {
where: 'account',
default: [],
},
mutedAds: {
where: 'account',
default: [] as string[],

View file

@ -31,7 +31,6 @@ export type Column = {
excludeTypes?: typeof notificationTypes[number][];
tl?: 'home' | 'local' |'media' | 'social' | 'global';
withRenotes?: boolean;
withReplies?: boolean;
onlyFiles?: boolean;
};

View file

@ -9,12 +9,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId"/>
<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes"/>
</XColumn>
</template>
<script lang="ts" setup>
import { } from 'vue';
import { watch } from 'vue';
import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store';
import MkTimeline from '@/components/MkTimeline.vue';
@ -27,11 +27,18 @@ const props = defineProps<{
}>();
let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
const withRenotes = $ref(props.column.withRenotes ?? true);
if (props.column.listId == null) {
setList();
}
watch($$(withRenotes), v => {
updateColumn(props.column.id, {
withRenotes: v,
});
});
async function setList() {
const lists = await os.api('users/lists/list');
const { canceled, result: list } = await os.select({
@ -62,5 +69,10 @@ const menu = [
text: i18n.ts.editList,
action: editList,
},
{
type: 'switch',
text: i18n.ts.showRenotes,
ref: $$(withRenotes),
},
];
</script>

View file

@ -24,10 +24,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkTimeline
v-else-if="column.tl"
ref="timeline"
:key="column.tl + withRenotes + withReplies + onlyFiles"
:key="column.tl + withRenotes + onlyFiles"
:src="column.tl"
:withRenotes="withRenotes"
:withReplies="withReplies"
:onlyFiles="onlyFiles"
/>
</XColumn>
@ -53,7 +52,6 @@ let disabled = $ref(false);
const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
const withRenotes = $ref(props.column.withRenotes ?? true);
const withReplies = $ref(props.column.withReplies ?? false);
const onlyFiles = $ref(props.column.onlyFiles ?? false);
watch($$(withRenotes), v => {
@ -62,12 +60,6 @@ watch($$(withRenotes), v => {
});
});
watch($$(withReplies), v => {
updateColumn(props.column.id, {
withReplies: v,
});
});
watch($$(onlyFiles), v => {
updateColumn(props.column.id, {
onlyFiles: v,
@ -118,10 +110,6 @@ const menu = [{
type: 'switch',
text: i18n.ts.showRenotes,
ref: $$(withRenotes),
}, {
type: 'switch',
text: i18n.ts.withReplies,
ref: $$(withReplies),
}, {
type: 'switch',
text: i18n.ts.fileAttachedOnly,