diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index dda45ceaa2..ed114d8d31 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -5,7 +5,16 @@ SPDX-License-Identifier: AGPL-3.0-only
+
();
const react = inject(DI.mfmEmojiReactCallback);
const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', ''));
const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@')));
+const emojiCodeToMute = makeEmojiMuteKey(props);
+const isMuted = checkEmojiMuted(emojiCodeToMute);
+const shouldMute = computed(() => !props.ignoreMuted && isMuted.value);
const rawUrl = computed(() => {
if (props.url) {
@@ -95,14 +109,18 @@ function onClick(ev: MouseEvent) {
menuItems.push({
type: 'label',
text: `:${props.name}:`,
- }, {
- text: i18n.ts.copy,
- icon: 'ti ti-copy',
- action: () => {
- copyToClipboard(`:${props.name}:`);
- },
});
+ if (isLocal.value) {
+ menuItems.push({
+ text: i18n.ts.copy,
+ icon: 'ti ti-copy',
+ action: () => {
+ copyToClipboard(`:${props.name}:`);
+ },
+ });
+ }
+
if (props.menuReaction && react) {
menuItems.push({
text: i18n.ts.doReaction,
@@ -113,21 +131,43 @@ function onClick(ev: MouseEvent) {
});
}
- menuItems.push({
- text: i18n.ts.info,
- icon: 'ti ti-info-circle',
- action: async () => {
- const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
- emoji: await misskeyApiGet('emoji', {
- name: customEmojiName.value,
- }),
- }, {
- closed: () => dispose(),
- });
- },
- });
+ if (isLocal.value) {
+ menuItems.push({
+ type: 'divider',
+ }, {
+ text: i18n.ts.info,
+ icon: 'ti ti-info-circle',
+ action: async () => {
+ const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
+ emoji: await misskeyApiGet('emoji', {
+ name: customEmojiName.value,
+ }),
+ }, {
+ closed: () => dispose(),
+ });
+ },
+ });
+ }
- if ($i?.isModerator ?? $i?.isAdmin) {
+ if (isMuted.value) {
+ menuItems.push({
+ text: i18n.ts.emojiUnmute,
+ icon: 'ti ti-mood-smile',
+ action: async () => {
+ await unmute();
+ },
+ });
+ } else {
+ menuItems.push({
+ text: i18n.ts.emojiMute,
+ icon: 'ti ti-mood-off',
+ action: async () => {
+ await mute();
+ },
+ });
+ }
+
+ if (($i?.isModerator ?? $i?.isAdmin) && isLocal.value) {
menuItems.push({
text: i18n.ts.edit,
icon: 'ti ti-pencil',
@@ -152,6 +192,36 @@ async function edit(name: string) {
});
}
+function mute() {
+ const titleEmojiName = isLocal.value
+ ? `:${customEmojiName.value}:`
+ : emojiCodeToMute;
+ os.confirm({
+ type: 'question',
+ title: i18n.tsx.muteX({ x: titleEmojiName }),
+ }).then(({ canceled }) => {
+ if (canceled) {
+ return;
+ }
+ muteEmoji(emojiCodeToMute);
+ });
+}
+
+function unmute() {
+ const titleEmojiName = isLocal.value
+ ? `:${customEmojiName.value}:`
+ : emojiCodeToMute;
+ os.confirm({
+ type: 'question',
+ title: i18n.tsx.unmuteX({ x: titleEmojiName }),
+ }).then(({ canceled }) => {
+ if (canceled) {
+ return;
+ }
+ unmuteEmoji(emojiCodeToMute);
+ });
+}
+
diff --git a/packages/frontend/src/components/global/MkSystemIcon.vue b/packages/frontend/src/components/global/MkSystemIcon.vue
index 20ce524e6d..d2ef0fb2d8 100644
--- a/packages/frontend/src/components/global/MkSystemIcon.vue
+++ b/packages/frontend/src/components/global/MkSystemIcon.vue
@@ -4,20 +4,33 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
@@ -25,18 +38,25 @@ SPDX-License-Identifier: AGPL-3.0-only
import {} from 'vue';
const props = defineProps<{
- type: 'info' | 'question' | 'warn' | 'error';
+ type: 'info' | 'question' | 'success' | 'warn' | 'error' | 'waiting';
}>();
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 49f716d886..1da16b8923 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -29,8 +29,8 @@ import { defineAsyncComponent, ref } from 'vue';
import { toUnicode as decodePunycode } from 'punycode.js';
import { url as local } from '@@/js/config.js';
import * as os from '@/os.js';
-import { useTooltip } from '@/use/use-tooltip.js';
-import { isEnabledUrlPreview } from '@/instance.js';
+import { useTooltip } from '@/composables/use-tooltip.js';
+import { isEnabledUrlPreview } from '@/utility/url-preview.js';
import type { MkABehavior } from '@/components/global/MkA.vue';
import { maybeMakeRelative } from '@@/js/url.js';
diff --git a/packages/frontend/src/components/global/PageWithHeader.vue b/packages/frontend/src/components/global/PageWithHeader.vue
index 33a34e0b67..d90afb652e 100644
--- a/packages/frontend/src/components/global/PageWithHeader.vue
+++ b/packages/frontend/src/components/global/PageWithHeader.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, useTemplateRef } from 'vue';
import { scrollInContainer } from '@@/js/scroll.js';
import type { PageHeaderProps } from './MkPageHeader.vue';
-import { useScrollPositionKeeper } from '@/use/use-scroll-position-keeper.js';
+import { useScrollPositionKeeper } from '@/composables/use-scroll-position-keeper.js';
import MkSwiper from '@/components/MkSwiper.vue';
import { useRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue
index 55de0df690..444509e6b3 100644
--- a/packages/frontend/src/components/grid/MkDataCell.vue
+++ b/packages/frontend/src/components/grid/MkDataCell.vue
@@ -95,7 +95,7 @@ import type { Size } from '@/components/grid/grid.js';
import type { CellValue, GridCell } from '@/components/grid/cell.js';
import type { GridRowSetting } from '@/components/grid/row.js';
import { GridEventEmitter } from '@/components/grid/grid.js';
-import { useTooltip } from '@/use/use-tooltip.js';
+import { useTooltip } from '@/composables/use-tooltip.js';
import * as os from '@/os.js';
import { equalCellAddress, getCellAddress } from '@/components/grid/grid-utils.js';
diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue
index f80f037285..a175485a7e 100644
--- a/packages/frontend/src/components/grid/MkGrid.vue
+++ b/packages/frontend/src/components/grid/MkGrid.vue
@@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index 7c2376249e..9b24501cce 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -49,6 +49,20 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+ {{ i18n.ts.emojiMute }}
+
+
+
+
+
+
+
import { ref, computed, watch } from 'vue';
+import XEmojiMute from './mute-block.emoji-mute.vue';
import XInstanceMute from './mute-block.instance-mute.vue';
import XWordMute from './mute-block.word-mute.vue';
import MkPagination from '@/components/MkPagination.vue';
diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue
index 4d718d21b4..18dfc2250c 100644
--- a/packages/frontend/src/pages/settings/preferences.vue
+++ b/packages/frontend/src/pages/settings/preferences.vue
@@ -41,6 +41,26 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+ {{ i18n.ts.realtimeMode }}
+ {{ i18n.ts._settings.realtimeMode_description }}
+
+
+
+
+
+
+
+ {{ i18n.ts._settings.contentsUpdateFrequency }}
+ {{ i18n.ts._settings.contentsUpdateFrequency_description }}
{{ i18n.ts._settings.contentsUpdateFrequency_description2 }}
+
+
+
+
+
+
+
@@ -148,22 +168,6 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
-
- {{ i18n.ts.showGapBetweenNotesInTimeline }}
-
-
-
-
-
-
-
- {{ i18n.ts.disableStreamingTimeline }}
-
-
-
-
{{ i18n.ts.pinnedList }}
@@ -553,6 +557,15 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+ reduceAnimation = !v">
+ {{ i18n.ts._settings.uiAnimations }}
+ {{ i18n.ts.turnOffToImprovePerformance }}
+
+
+
+
@@ -571,6 +584,15 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+ {{ i18n.ts._settings.enableHighQualityImagePlaceholders }}
+ {{ i18n.ts.turnOffToImprovePerformance }}
+
+
+
+
@@ -579,6 +601,24 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+
{{ i18n.ts._clientPerformanceIssueTip.title }}
+
+
{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledAdBlocker }}
+
{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledAdBlocker_description }}
+
+
+
{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledCustomCss }}
+
{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledCustomCss_description }}
+
+
+
{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledAddons }}
+
{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledAddons_description }}
+
+
+
@@ -604,9 +644,13 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._dataSaver._avatar.title }}
{{ i18n.ts._dataSaver._avatar.description }}
-
- {{ i18n.ts._dataSaver._urlPreview.title }}
- {{ i18n.ts._dataSaver._urlPreview.description }}
+
+ {{ i18n.ts._dataSaver._disableUrlPreview.title }}
+ {{ i18n.ts._dataSaver._disableUrlPreview.description }}
+
+
+ {{ i18n.ts._dataSaver._urlPreviewThumbnail.title }}
+ {{ i18n.ts._dataSaver._urlPreviewThumbnail.description }}
{{ i18n.ts._dataSaver._code.title }}
@@ -734,7 +778,7 @@ import MkRadios from '@/components/MkRadios.vue';
import MkRange from '@/components/MkRange.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
-import FormSection from '@/components/form/section.vue';
+import MkDisableSection from '@/components/MkDisableSection.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue';
import MkInfo from '@/components/MkInfo.vue';
@@ -757,8 +801,10 @@ const $i = ensureSignin();
const lang = ref(miLocalStorage.getItem('lang'));
const dataSaver = ref(prefer.s.dataSaver);
+const realtimeMode = computed(store.makeGetterSetter('realtimeMode'));
const overridedDeviceKind = prefer.model('overridedDeviceKind');
+const pollingInterval = prefer.model('pollingInterval');
const showTitlebar = prefer.model('showTitlebar');
const keepCw = prefer.model('keepCw');
const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior');
@@ -777,7 +823,6 @@ const showFixedPostFormInChannel = prefer.model('showFixedPostFormInChannel');
const numberOfPageCache = prefer.model('numberOfPageCache');
const enableInfiniteScroll = prefer.model('enableInfiniteScroll');
const useReactionPickerForContextMenu = prefer.model('useReactionPickerForContextMenu');
-const disableStreamingTimeline = prefer.model('disableStreamingTimeline');
const useGroupedNotifications = prefer.model('useGroupedNotifications');
const alwaysConfirmFollow = prefer.model('alwaysConfirmFollow');
const confirmWhenRevealingSensitiveMedia = prefer.model('confirmWhenRevealingSensitiveMedia');
@@ -785,7 +830,6 @@ const confirmOnReact = prefer.model('confirmOnReact');
const defaultNoteVisibility = prefer.model('defaultNoteVisibility');
const defaultNoteLocalOnly = prefer.model('defaultNoteLocalOnly');
const rememberNoteVisibility = prefer.model('rememberNoteVisibility');
-const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline');
const notificationPosition = prefer.model('notificationPosition');
const notificationStackAxis = prefer.model('notificationStackAxis');
const instanceTicker = prefer.model('instanceTicker');
@@ -804,6 +848,7 @@ const defaultFollowWithReplies = prefer.model('defaultFollowWithReplies');
const chatShowSenderName = prefer.model('chat.showSenderName');
const chatSendOnEnter = prefer.model('chat.sendOnEnter');
const useStickyIcons = prefer.model('useStickyIcons');
+const enableHighQualityImagePlaceholders = prefer.model('enableHighQualityImagePlaceholders');
const reduceAnimation = prefer.model('animation', v => !v, v => !v);
const animatedMfm = prefer.model('animatedMfm');
const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages');
@@ -843,13 +888,13 @@ watch(useSystemFont, () => {
watch([
hemisphere,
lang,
+ realtimeMode,
+ pollingInterval,
enableInfiniteScroll,
showNoteActionsOnlyHover,
overridedDeviceKind,
- disableStreamingTimeline,
alwaysConfirmFollow,
confirmWhenRevealingSensitiveMedia,
- showGapBetweenNotesInTimeline,
mediaListWithOneImageAppearance,
reactionsDisplaySize,
limitWidthOfReaction,
@@ -862,6 +907,7 @@ watch([
enableSeasonalScreenEffect,
chatShowSenderName,
useStickyIcons,
+ enableHighQualityImagePlaceholders,
keepScreenOn,
contextMenu,
fontSize,
@@ -869,6 +915,7 @@ watch([
makeEveryTextElementsSelectable,
enableHorizontalSwipe,
enablePullToRefresh,
+ reduceAnimation,
], async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
});
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 30b7cf9a86..cd1565f39e 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -161,7 +161,7 @@ import MkSelect from '@/components/MkSelect.vue';
import FormSplit from '@/components/form/split.vue';
import MkFolder from '@/components/MkFolder.vue';
import FormSlot from '@/components/form/slot.vue';
-import { selectFile } from '@/utility/select-file.js';
+import { chooseDriveFile } from '@/utility/drive.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { ensureSignin } from '@/i.js';
@@ -257,54 +257,100 @@ function save() {
}
function changeAvatar(ev) {
- selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
- let originalOrCropped = file;
-
- const { canceled } = await os.confirm({
- type: 'question',
- text: i18n.ts.cropImageAsk,
- okText: i18n.ts.cropYes,
- cancelText: i18n.ts.cropNo,
- });
-
- if (!canceled) {
- originalOrCropped = await os.cropImage(file, {
- aspectRatio: 1,
- });
- }
-
+ async function done(driveFile) {
const i = await os.apiWithDialog('i/update', {
- avatarId: originalOrCropped.id,
+ avatarId: driveFile.id,
});
$i.avatarId = i.avatarId;
$i.avatarUrl = i.avatarUrl;
claimAchievement('profileFilled');
- });
+ }
+
+ os.popupMenu([{
+ text: i18n.ts.avatar,
+ type: 'label',
+ }, {
+ text: i18n.ts.upload,
+ icon: 'ti ti-upload',
+ action: async () => {
+ const files = await os.chooseFileFromPc({ multiple: false });
+ const file = files[0];
+
+ let originalOrCropped = file;
+
+ const { canceled } = await os.confirm({
+ type: 'question',
+ text: i18n.ts.cropImageAsk,
+ okText: i18n.ts.cropYes,
+ cancelText: i18n.ts.cropNo,
+ });
+
+ if (!canceled) {
+ originalOrCropped = await os.cropImageFile(file, {
+ aspectRatio: 1,
+ });
+ }
+
+ const driveFile = (await os.launchUploader([originalOrCropped], { multiple: false }))[0];
+ done(driveFile);
+ },
+ }, {
+ text: i18n.ts.fromDrive,
+ icon: 'ti ti-cloud',
+ action: () => {
+ chooseDriveFile({ multiple: false }).then(files => {
+ done(files[0]);
+ });
+ },
+ }], ev.currentTarget ?? ev.target);
}
function changeBanner(ev) {
- selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
- let originalOrCropped = file;
-
- const { canceled } = await os.confirm({
- type: 'question',
- text: i18n.ts.cropImageAsk,
- okText: i18n.ts.cropYes,
- cancelText: i18n.ts.cropNo,
- });
-
- if (!canceled) {
- originalOrCropped = await os.cropImage(file, {
- aspectRatio: 2,
- });
- }
-
+ async function done(driveFile) {
const i = await os.apiWithDialog('i/update', {
- bannerId: originalOrCropped.id,
+ bannerId: driveFile.id,
});
$i.bannerId = i.bannerId;
$i.bannerUrl = i.bannerUrl;
- });
+ }
+
+ os.popupMenu([{
+ text: i18n.ts.banner,
+ type: 'label',
+ }, {
+ text: i18n.ts.upload,
+ icon: 'ti ti-upload',
+ action: async () => {
+ const files = await os.chooseFileFromPc({ multiple: false });
+ const file = files[0];
+
+ let originalOrCropped = file;
+
+ const { canceled } = await os.confirm({
+ type: 'question',
+ text: i18n.ts.cropImageAsk,
+ okText: i18n.ts.cropYes,
+ cancelText: i18n.ts.cropNo,
+ });
+
+ if (!canceled) {
+ originalOrCropped = await os.cropImageFile(file, {
+ aspectRatio: 2,
+ });
+ }
+
+ const driveFile = (await os.launchUploader([originalOrCropped], { multiple: false }))[0];
+ done(driveFile);
+ },
+ }, {
+ text: i18n.ts.fromDrive,
+ icon: 'ti ti-cloud',
+ action: () => {
+ chooseDriveFile({ multiple: false }).then(files => {
+ done(files[0]);
+ });
+ },
+ }], ev.currentTarget ?? ev.target);
}
const headerActions = computed(() => []);
diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue
index 1bac19fe47..ffbbefa122 100644
--- a/packages/frontend/src/pages/settings/sounds.sound.vue
+++ b/packages/frontend/src/pages/settings/sounds.sound.vue
@@ -40,7 +40,7 @@ import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js';
-import { selectFile } from '@/utility/select-file.js';
+import { selectFile } from '@/utility/drive.js';
const props = defineProps<{
type: SoundType;
diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue
index e1dffd4f2d..d1e5db5a5b 100644
--- a/packages/frontend/src/pages/tag.vue
+++ b/packages/frontend/src/pages/tag.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -19,8 +19,8 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue
index 49d015a530..d8eca07a42 100644
--- a/packages/frontend/src/pages/user/index.timeline.vue
+++ b/packages/frontend/src/pages/user/index.timeline.vue
@@ -8,19 +8,19 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
-
+
+
+
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index d131c17340..c2cf937c71 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -17,13 +17,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
{{ instance.host }}
-
+
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import XTimeline from './welcome.timeline.vue';
-import MarqueeText from '@/components/MkMarquee.vue';
+import MkMarqueeText from '@/components/MkMarqueeText.vue';
import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
import misskeysvg from '/client-assets/misskey.svg';
import { misskeyApiGet } from '@/utility/misskey-api.js';
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index 69a654595a..675e82a71d 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -6,39 +6,126 @@ SPDX-License-Identifier: AGPL-3.0-only
@@ -108,15 +116,16 @@ const router = useRouter();
const props = defineProps<{
showWidgetButton?: boolean;
+ asDrawer?: boolean;
}>();
const emit = defineEmits<{
(ev: 'widgetButtonClick'): void;
}>();
-const forceIconOnly = ref(window.innerWidth <= 1279);
+const forceIconOnly = ref(!props.asDrawer && window.innerWidth <= 1279);
const iconOnly = computed(() => {
- return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon');
+ return !props.asDrawer && (forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon'));
});
const otherMenuItemIndicated = computed(() => {
@@ -147,6 +156,20 @@ function toggleIconOnly() {
}
}
+function toggleRealtimeMode(ev: MouseEvent) {
+ os.popupMenu([{
+ type: 'label',
+ text: i18n.ts.realtimeMode,
+ }, {
+ text: store.s.realtimeMode ? i18n.ts.turnItOff : i18n.ts.turnItOn,
+ icon: store.s.realtimeMode ? 'ti ti-bolt-off' : 'ti ti-bolt',
+ action: () => {
+ store.set('realtimeMode', !store.s.realtimeMode);
+ window.location.reload();
+ },
+ }], ev.currentTarget ?? ev.target);
+}
+
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_({
withExtraOperation: true,
@@ -191,21 +214,108 @@ function menuEdit() {
overscroll-behavior: contain;
background: var(--MI_THEME-navBg);
contain: strict;
+
+ /* 画面が縦に長い、設置している項目数が少ないなどの環境においても確実にbottomを最下部に表示するため */
display: flex;
flex-direction: column;
- direction: rtl; // スクロールバーを左に表示したいため
+
+ direction: rtl; /* スクロールバーを左に表示したいため */
}
.top {
+ flex-shrink: 0;
direction: ltr;
+
+ /* 疑似progressive blur */
+ &::before {
+ position: absolute;
+ z-index: -1;
+ inset: 0;
+ content: "";
+ backdrop-filter: blur(8px);
+ mask-image: linear-gradient(
+ to top,
+ rgb(0 0 0 / 0%) 0%,
+ rgb(0 0 0 / 4.9%) 7.75%,
+ rgb(0 0 0 / 10.4%) 11.25%,
+ rgb(0 0 0 / 45%) 23.55%,
+ rgb(0 0 0 / 55%) 26.45%,
+ rgb(0 0 0 / 89.6%) 38.75%,
+ rgb(0 0 0 / 95.1%) 42.25%,
+ rgb(0 0 0 / 100%) 50%
+ );
+ }
+
+ &::after {
+ position: absolute;
+ z-index: -1;
+ inset: 0;
+ bottom: 25%;
+ content: "";
+ backdrop-filter: blur(16px);
+ mask-image: linear-gradient(
+ to top,
+ rgb(0 0 0 / 0%) 0%,
+ rgb(0 0 0 / 4.9%) 15.5%,
+ rgb(0 0 0 / 10.4%) 22.5%,
+ rgb(0 0 0 / 45%) 47.1%,
+ rgb(0 0 0 / 55%) 52.9%,
+ rgb(0 0 0 / 89.6%) 77.5%,
+ rgb(0 0 0 / 95.1%) 91.9%,
+ rgb(0 0 0 / 100%) 100%
+ );
+ }
}
.middle {
+ flex: 1;
direction: ltr;
}
.bottom {
+ flex-shrink: 0;
direction: ltr;
+
+ /* 疑似progressive blur */
+ &::before {
+ position: absolute;
+ z-index: -1;
+ inset: -30px 0 0 0;
+ content: "";
+ backdrop-filter: blur(8px);
+ mask-image: linear-gradient(
+ to bottom,
+ rgb(0 0 0 / 0%) 0%,
+ rgb(0 0 0 / 4.9%) 7.75%,
+ rgb(0 0 0 / 10.4%) 11.25%,
+ rgb(0 0 0 / 45%) 23.55%,
+ rgb(0 0 0 / 55%) 26.45%,
+ rgb(0 0 0 / 89.6%) 38.75%,
+ rgb(0 0 0 / 95.1%) 42.25%,
+ rgb(0 0 0 / 100%) 50%
+ );
+ pointer-events: none;
+ }
+
+ &::after {
+ position: absolute;
+ z-index: -1;
+ inset: 0;
+ top: 25%;
+ content: "";
+ backdrop-filter: blur(16px);
+ mask-image: linear-gradient(
+ to bottom,
+ rgb(0 0 0 / 0%) 0%,
+ rgb(0 0 0 / 4.9%) 15.5%,
+ rgb(0 0 0 / 10.4%) 22.5%,
+ rgb(0 0 0 / 45%) 47.1%,
+ rgb(0 0 0 / 55%) 52.9%,
+ rgb(0 0 0 / 89.6%) 77.5%,
+ rgb(0 0 0 / 95.1%) 91.9%,
+ rgb(0 0 0 / 100%) 100%
+ );
+ }
}
.subButtons {
@@ -290,29 +400,19 @@ function menuEdit() {
}
.top {
+ --top-height: 80px;
+
position: sticky;
top: 0;
z-index: 1;
- padding: 20px 0;
- background: var(--nav-bg-transparent);
- -webkit-backdrop-filter: var(--MI-blur, blur(8px));
- backdrop-filter: var(--MI-blur, blur(8px));
+ display: flex;
+ height: var(--top-height);
+ padding-left: 6px;
}
.instance {
position: relative;
- display: block;
- text-align: center;
- width: 100%;
-
- &:focus-visible {
- outline: none;
-
- > .instanceIcon {
- outline: 2px solid var(--MI_THEME-focus);
- outline-offset: 2px;
- }
- }
+ width: var(--top-height);
}
.instanceIcon {
@@ -322,13 +422,20 @@ function menuEdit() {
border-radius: 8px;
}
+ .realtimeMode {
+ display: inline-block;
+ width: var(--top-height);
+ margin-left: auto;
+
+ &.on {
+ color: var(--MI_THEME-accent);
+ }
+ }
+
.bottom {
position: sticky;
bottom: 0;
padding-top: 20px;
- background: var(--nav-bg-transparent);
- -webkit-backdrop-filter: var(--MI-blur, blur(8px));
- backdrop-filter: var(--MI-blur, blur(8px));
}
.post {
@@ -416,10 +523,6 @@ function menuEdit() {
padding-right: 8px;
}
- .middle {
- flex: 1;
- }
-
.divider {
margin: 16px 16px;
border-top: solid 0.5px var(--MI_THEME-divider);
@@ -520,9 +623,6 @@ function menuEdit() {
top: 0;
z-index: 1;
padding: 20px 0;
- background: var(--nav-bg-transparent);
- -webkit-backdrop-filter: var(--MI-blur, blur(8px));
- backdrop-filter: var(--MI-blur, blur(8px));
}
.instance {
@@ -551,9 +651,6 @@ function menuEdit() {
position: sticky;
bottom: 0;
padding-top: 20px;
- background: var(--nav-bg-transparent);
- -webkit-backdrop-filter: var(--MI-blur, blur(8px));
- backdrop-filter: var(--MI-blur, blur(8px));
}
.widget {
@@ -564,6 +661,18 @@ function menuEdit() {
text-align: center;
}
+ .realtimeMode {
+ display: block;
+ position: relative;
+ width: 100%;
+ height: 52px;
+ text-align: center;
+
+ &.on {
+ color: var(--MI_THEME-accent);
+ }
+ }
+
.post {
display: block;
position: relative;
@@ -637,10 +746,6 @@ function menuEdit() {
display: none;
}
- .middle {
- flex: 1;
- }
-
.divider {
margin: 8px auto;
width: calc(100% - 32px);
@@ -650,7 +755,7 @@ function menuEdit() {
.item {
display: block;
position: relative;
- padding: 18px 0;
+ padding: 16px 0;
width: 100%;
text-align: center;
diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue
index 16e72fa227..7248e8826b 100644
--- a/packages/frontend/src/ui/_common_/statusbar-federation.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:leaveToClass="$style.transition_change_leaveTo"
mode="default"
>
-
+
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index 194b56c842..716f0ba995 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ column.name || antennaName || i18n.ts._deck._columns.antenna }}
-
+
@@ -21,13 +21,12 @@ import type { Column } from '@/deck.js';
import type { MenuItem } from '@/types/menu.js';
import type { SoundStore } from '@/preferences/def.js';
import { updateColumn } from '@/deck.js';
-import MkTimeline from '@/components/MkTimeline.vue';
+import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { antennasCache } from '@/cache.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
-import * as sound from '@/utility/sound.js';
const props = defineProps<{
column: Column;
@@ -96,10 +95,6 @@ function editAntenna() {
os.pageWindow('my/antennas/' + props.column.antennaId);
}
-function onNote() {
- sound.playMisskeySfxFile(soundSetting.value);
-}
-
const menu: MenuItem[] = [
{
icon: 'ti ti-pencil',
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index c2644da707..3439a2a56e 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -26,14 +26,13 @@ import type { Column } from '@/deck.js';
import type { MenuItem } from '@/types/menu.js';
import type { SoundStore } from '@/preferences/def.js';
import { updateColumn } from '@/deck.js';
-import MkTimeline from '@/components/MkTimeline.vue';
+import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { favoritedChannelsCache } from '@/cache.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
-import * as sound from '@/utility/sound.js';
const props = defineProps<{
column: Column;
@@ -90,10 +89,6 @@ async function post() {
});
}
-function onNote() {
- sound.playMisskeySfxFile(soundSetting.value);
-}
-
const menu: MenuItem[] = [{
icon: 'ti ti-pencil',
text: i18n.ts.selectChannel,
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 2085c73e03..4e79b301e3 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -51,6 +51,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
+import { checkDragDataType, getDragData, setDragData } from '@/drag-and-drop.js';
provide('shouldHeaderThin', true);
provide('shouldOmitHeaderTitle', true);
@@ -262,7 +263,7 @@ function goTop() {
function onDragstart(ev) {
ev.dataTransfer.effectAllowed = 'move';
- ev.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id);
+ setDragData(ev, 'deckColumn', props.column.id);
// Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう
// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
@@ -281,7 +282,7 @@ function onDragover(ev) {
// 自分自身にはドロップさせない
ev.dataTransfer.dropEffect = 'none';
} else {
- const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_;
+ const isDeckColumn = checkDragDataType(ev, ['deckColumn']);
ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
@@ -297,8 +298,8 @@ function onDrop(ev) {
draghover.value = false;
os.deckGlobalEvents.emit('column.dragEnd');
- const id = ev.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_);
- if (id != null && id !== '') {
+ const id = getDragData(ev, 'deckColumn');
+ if (id != null) {
swapColumn(props.column.id, id);
}
}
diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue
index 772188d773..c8b174da09 100644
--- a/packages/frontend/src/ui/deck/direct-column.vue
+++ b/packages/frontend/src/ui/deck/direct-column.vue
@@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ column.name || i18n.ts._deck._columns.direct }}
-
+