diff options
| author | Hazelnoot <acomputerdog@gmail.com> | 2025-03-31 16:07:41 -0400 |
|---|---|---|
| committer | Hazelnoot <acomputerdog@gmail.com> | 2025-03-31 16:07:55 -0400 |
| commit | 2983092c068d62e5c9bb10ffcef36f34c885910a (patch) | |
| tree | bb3c85ca8eb713ab116e5e19f108b56cea5ab4b9 | |
| parent | Merge branch 'develop' into merge/2025-03-24 (diff) | |
| parent | fix missing import (diff) | |
| download | sharkey-2983092c068d62e5c9bb10ffcef36f34c885910a.tar.gz sharkey-2983092c068d62e5c9bb10ffcef36f34c885910a.tar.bz2 sharkey-2983092c068d62e5c9bb10ffcef36f34c885910a.zip | |
Merge branch 'misskey-develop' into merge/2025-03-24
59 files changed, 633 insertions, 251 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 731346621c..5971e3fcb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,9 @@ - Enhance: プラグインの管理が強化されました - インストール/アンインストール/設定の変更時にリロード不要になりました - Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように +- Enhance: デッキUIでカラム間のマージンを設定できるように +- Enhance: デッキUIでデッキメニューの位置を設定できるように +- Enhance: デッキUIでナビゲーションバーの位置を設定できるように - Enhance: アイコンのスクロール追従を無効化してパフォーマンス向上できるように - Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに - Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように @@ -52,8 +55,7 @@ - Fix: 読み込み直後にスクロールしようとすると途中で止まる場合があるのを修正 - Fix: テーマ切り替え時に一部の色が変わらない問題を修正 - NOTE: 構造上クラシックUIを新しいデザインシステムに移行することが困難なため、クラシックUIが削除されました - - デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置することである程度クラシックUIを再現できます - - また、デッキでナビゲーションバーを上部に表示するオプションを実装予定です + - デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置し、ナビゲーションバーを上部に表示することである程度クラシックUIを再現できます ### Server - Enhance 全体的なパフォーマンス向上 diff --git a/locales/index.d.ts b/locales/index.d.ts index 8732c34230..2e1c92f910 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5363,6 +5363,18 @@ export interface Locale extends ILocale { * 圧縮 */ "compress": string; + /** + * 右 + */ + "right": string; + /** + * 下 + */ + "bottom": string; + /** + * 上 + */ + "top": string; "_chat": { /** * まだメッセージはありません @@ -5498,7 +5510,7 @@ export interface Locale extends ILocale { */ "thisUserAllowsChatOnlyFromFollowers": string; /** - * このユーザーはフォローしているユーザーからのみチャットを受け付けています。 + * このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。 */ "thisUserAllowsChatOnlyFromFollowing": string; /** @@ -10246,6 +10258,18 @@ export interface Locale extends ILocale { */ "columnAlign": string; /** + * カラム間のマージン + */ + "columnGap": string; + /** + * デッキメニューの位置 + */ + "deckMenuPosition": string; + /** + * ナビゲーションバーの位置 + */ + "navbarPosition": string; + /** * カラムを追加 */ "addColumn": string; @@ -10298,7 +10322,7 @@ export interface Locale extends ILocale { */ "introduction": string; /** - * 画面の右にある + を押して、いつでもカラムを追加できます。 + * カラムを追加するには、画面の + をクリックします。 */ "introduction2": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index eb1505270c..639b094a81 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1336,6 +1336,9 @@ chat: "チャット" migrateOldSettings: "旧設定情報を移行" migrateOldSettings_description: "通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。" compress: "圧縮" +right: "右" +bottom: "下" +top: "上" _chat: noMessagesYet: "まだメッセージはありません" @@ -1371,7 +1374,7 @@ _chat: cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。" chatWithThisUser: "チャットする" thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみチャットを受け付けています。" - thisUserAllowsChatOnlyFromFollowing: "このユーザーはフォローしているユーザーからのみチャットを受け付けています。" + thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。" thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみチャットを受け付けています。" thisUserNotAllowedChatAnyone: "このユーザーは誰からもチャットを受け付けていません。" chatAllowedUsers: "チャットを許可する相手" @@ -2662,6 +2665,9 @@ _notification: _deck: alwaysShowMainColumn: "常にメインカラムを表示" columnAlign: "カラムの寄せ" + columnGap: "カラム間のマージン" + deckMenuPosition: "デッキメニューの位置" + navbarPosition: "ナビゲーションバーの位置" addColumn: "カラムを追加" newNoteNotificationSettings: "新着ノート通知の設定" configureColumn: "カラムの設定" @@ -2675,7 +2681,7 @@ _deck: newProfile: "新規プロファイル" deleteProfile: "プロファイルを削除" introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょう!" - introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。" + introduction2: "カラムを追加するには、画面の + をクリックします。" widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください" useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示" usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります" diff --git a/package.json b/package.json index fb71b3871e..defef33539 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sharkey", - "version": "2025.3.2-beta.18", + "version": "2025.3.2-beta.20", "codename": "shonk", "repository": { "type": "git", diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue index 3bdf702b01..e9126d4665 100644 --- a/packages/frontend-embed/src/components/EmMediaImage.vue +++ b/packages/frontend-embed/src/components/EmMediaImage.vue @@ -95,7 +95,7 @@ async function onclick(ev: MouseEvent) { position: absolute; border-radius: 6px; background-color: var(--MI_THEME-fg); - color: var(--MI_THEME-accentLighten); + color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); font-size: 12px; opacity: .5; padding: 5px 8px; @@ -153,7 +153,7 @@ html[data-color-scheme=light] .visible { /* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */ background-color: black; border-radius: 6px; - color: var(--MI_THEME-accentLighten); + color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); display: inline-block; font-weight: bold; font-size: 0.8em; diff --git a/packages/frontend-embed/src/style.scss b/packages/frontend-embed/src/style.scss index 6097281c37..ba3238cd4c 100644 --- a/packages/frontend-embed/src/style.scss +++ b/packages/frontend-embed/src/style.scss @@ -278,7 +278,7 @@ rt { } ._acrylic { - background: var(--MI_THEME-acrylicPanel); + background: color(from var(--MI_THEME-panel) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); } diff --git a/packages/frontend-shared/themes/_dark.json5 b/packages/frontend-shared/themes/_dark.json5 index 1f7620762c..a31f1ed825 100644 --- a/packages/frontend-shared/themes/_dark.json5 +++ b/packages/frontend-shared/themes/_dark.json5 @@ -10,13 +10,10 @@ props: { accent: '#86b300', - accentDarken: ':darken<10<@accent', - accentLighten: ':lighten<10<@accent', accentedBg: ':alpha<0.15<@accent', love: '#dd2e44', focus: ':alpha<0.3<@accent', bg: '#000', - acrylicBg: ':alpha<0.5<@bg', fg: '#dadada', fgTransparentWeak: ':alpha<0.75<@fg', fgTransparent: ':alpha<0.5<@fg', @@ -32,7 +29,6 @@ panelHeaderDivider: 'rgba(0, 0, 0, 0)', panelBorder: '" solid 1px var(--MI_THEME-divider)', thread: ':lighten<12<@panel', - acrylicPanel: ':alpha<0.5<@panel', windowHeader: ':alpha<0.85<@panel', popup: ':lighten<3<@panel', shadow: 'rgba(0, 0, 0, 0.3)', diff --git a/packages/frontend-shared/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5 index 87d921d43b..54aee7c21e 100644 --- a/packages/frontend-shared/themes/_light.json5 +++ b/packages/frontend-shared/themes/_light.json5 @@ -10,13 +10,10 @@ props: { accent: '#86b300', - accentDarken: ':darken<10<@accent', - accentLighten: ':lighten<10<@accent', accentedBg: ':alpha<0.15<@accent', love: '#dd2e44', focus: ':alpha<0.3<@accent', bg: '#fff', - acrylicBg: ':alpha<0.5<@bg', fg: '#5f5f5f', fgTransparentWeak: ':alpha<0.75<@fg', fgTransparent: ':alpha<0.5<@fg', @@ -32,7 +29,6 @@ panelHeaderDivider: 'rgba(0, 0, 0, 0)', panelBorder: '" solid 1px var(--MI_THEME-divider)', thread: ':darken<12<@panel', - acrylicPanel: ':alpha<0.5<@panel', windowHeader: ':alpha<0.85<@panel', popup: ':lighten<3<@panel', shadow: 'rgba(0, 0, 0, 0.1)', diff --git a/packages/frontend-shared/themes/d-astro.json5 b/packages/frontend-shared/themes/d-astro.json5 index e8864df336..371a98ba27 100644 --- a/packages/frontend-shared/themes/d-astro.json5 +++ b/packages/frontend-shared/themes/d-astro.json5 @@ -25,7 +25,6 @@ mention: '#ffd152', modalBg: 'rgba(0, 0, 0, 0.5)', success: '#86b300', - acrylicBg: ':alpha<0.5<@bg', indicator: '@accent', mentionMe: '#fb5d38', messageBg: '@bg', @@ -37,10 +36,7 @@ inputBorder: 'rgba(255, 255, 255, 0.1)', inputBorderHover: 'rgba(255, 255, 255, 0.2)', panelBorder: '" solid 1px var(--MI_THEME-divider)', - accentDarken: ':darken<10<@accent', - acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@accent', - accentLighten: ':lighten<10<@accent', buttonGradateA: '@accent', buttonGradateB: ':hue<-20<@accent', driveFolderBg: ':alpha<0.3<@accent', diff --git a/packages/frontend-shared/themes/d-u0.json5 b/packages/frontend-shared/themes/d-u0.json5 index 0223b1fb5c..57b97c5d75 100644 --- a/packages/frontend-shared/themes/d-u0.json5 +++ b/packages/frontend-shared/themes/d-u0.json5 @@ -31,7 +31,6 @@ modalBg: 'rgba(0, 0, 0, 0.5)', success: '#86b300', switchBg: 'rgba(255, 255, 255, 0.15)', - acrylicBg: ':alpha<0.5<@bg', indicator: '@accent', mentionMe: '@mention', messageBg: '@bg', @@ -48,10 +47,7 @@ dateLabelFg: '@fg', inputBorder: 'rgba(255, 255, 255, 0.1)', panelBorder: '" solid 1px var(--MI_THEME-divider)', - accentDarken: ':darken<10<@accent', - acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@indicator', - accentLighten: ':lighten<10<@accent', driveFolderBg: ':alpha<0.3<@accent', fgHighlighted: ':lighten<3<@fg', fgTransparent: ':alpha<0.5<@fg', diff --git a/packages/frontend-shared/themes/l-u0.json5 b/packages/frontend-shared/themes/l-u0.json5 index f6023af819..dd37ca2781 100644 --- a/packages/frontend-shared/themes/l-u0.json5 +++ b/packages/frontend-shared/themes/l-u0.json5 @@ -32,7 +32,6 @@ success: '#86b300', buttonBg: '#0000000d', switchBg: 'rgba(255, 255, 255, 0.15)', - acrylicBg: ':alpha<0.5<@bg', indicator: '@accent', mentionMe: '@mention', messageBg: '@bg', @@ -49,10 +48,7 @@ dateLabelFg: '@fg', inputBorder: 'rgba(255, 255, 255, 0.1)', panelBorder: '" solid 1px var(--MI_THEME-divider)', - accentDarken: ':darken<10<@accent', - acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@indicator', - accentLighten: ':lighten<10<@accent', buttonHoverBg: '#0000001a', driveFolderBg: ':alpha<0.3<@accent', fgHighlighted: ':lighten<3<@fg', diff --git a/packages/frontend-shared/themes/l-vivid.json5 b/packages/frontend-shared/themes/l-vivid.json5 index 058c9c32e5..b3c0343e27 100644 --- a/packages/frontend-shared/themes/l-vivid.json5 +++ b/packages/frontend-shared/themes/l-vivid.json5 @@ -28,7 +28,6 @@ mention: '@accent', modalBg: 'rgba(0, 0, 0, 0.3)', success: '#86b300', - acrylicBg: ':alpha<0.5<@bg', indicator: '@accent', mentionMe: '@mention', messageBg: '@bg', @@ -40,10 +39,7 @@ inputBorder: 'rgba(0, 0, 0, 0.1)', inputBorderHover: 'rgba(0, 0, 0, 0.2)', panelBorder: '" solid 1px var(--MI_THEME-divider)', - accentDarken: ':darken<10<@accent', - acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@accent', - accentLighten: ':lighten<10<@accent', driveFolderBg: ':alpha<0.3<@accent', fgHighlighted: ':darken<3<@fg', fgTransparent: ':alpha<0.5<@fg', diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index c73cf840ac..321b043d9e 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -420,7 +420,7 @@ onBeforeUnmount(() => { } &:active { - background: var(--MI_THEME-accentDarken); + background: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); color: #fff !important; } } diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue index 7ee1e98b48..48f2f328f6 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.vue +++ b/packages/frontend/src/components/MkChannelFollowButton.vue @@ -103,13 +103,13 @@ async function onClick() { background: var(--MI_THEME-accent); &:hover { - background: var(--MI_THEME-accentLighten); - border-color: var(--MI_THEME-accentLighten); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); + border-color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); } &:active { - background: var(--MI_THEME-accentDarken); - border-color: var(--MI_THEME-accentDarken); + background: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); + border-color: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); } } diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 22a2673f33..61c02c49b5 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -155,11 +155,11 @@ function onDragend() { background: var(--MI_THEME-accent); &:hover { - background: var(--MI_THEME-accentLighten); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); } &:active { - background: var(--MI_THEME-accentDarken); + background: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); } > .label { diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 0d062d0bd8..47eb15c68c 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -239,7 +239,7 @@ onMounted(() => { bottom: var(--MI-stickyBottom, 0px); left: 0; padding: 12px; - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); background-size: auto auto; diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 3ae56dbe8d..1d86af39e5 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -227,13 +227,13 @@ onBeforeUnmount(() => { background: var(--MI_THEME-accent); &:hover { - background: var(--MI_THEME-accentLighten); - border-color: var(--MI_THEME-accentLighten); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); + border-color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); } &:active { - background: var(--MI_THEME-accentDarken); - border-color: var(--MI_THEME-accentDarken); + background: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); + border-color: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); } } diff --git a/packages/frontend/src/components/MkFukidashi.vue b/packages/frontend/src/components/MkFukidashi.vue index e9544afa35..fba5dc854c 100644 --- a/packages/frontend/src/components/MkFukidashi.vue +++ b/packages/frontend/src/components/MkFukidashi.vue @@ -51,7 +51,7 @@ withDefaults(defineProps<{ padding-top: calc(var(--fukidashi-radius) * .13); &.accented { - --fukidashi-bg: var(--MI_THEME-accent); + --fukidashi-bg: color-mix(in srgb, var(--MI_THEME-accent), var(--MI_THEME-panel) 85%); } &.shadow { @@ -87,6 +87,12 @@ withDefaults(defineProps<{ padding: 10px 14px; } +@container (max-width: 450px) { + .content { + padding: 8px 12px; + } +} + .tail { position: absolute; top: 0; diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 2b59ed9f85..54a1d8f324 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -221,7 +221,7 @@ function showMenu(ev: MouseEvent) { position: absolute; border-radius: var(--MI-radius-sm); background-color: black; - color: var(--MI_THEME-accentLighten); + color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); font-size: 12px; opacity: .5; padding: 5px 8px; @@ -295,7 +295,7 @@ html[data-color-scheme=light] .visible { /* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */ background-color: black; border-radius: var(--MI-radius-sm); - color: var(--MI_THEME-accentLighten); + color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); display: inline-block; font-weight: bold; font-size: 0.8em; diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 041257b06f..a335ff4575 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -556,7 +556,7 @@ onDeactivated(() => { /* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */ background-color: black; border-radius: 6px; - color: var(--MI_THEME-accentLighten); + color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); display: inline-block; font-weight: bold; font-size: 0.8em; @@ -568,7 +568,7 @@ onDeactivated(() => { position: absolute; border-radius: var(--MI-radius-sm); background-color: black; - color: var(--MI_THEME-accentLighten); + color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); font-size: 12px; opacity: .5; padding: 5px 8px; diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue index bc72351552..e03251ad4f 100644 --- a/packages/frontend/src/components/MkNotes.vue +++ b/packages/frontend/src/components/MkNotes.vue @@ -23,10 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only tag="div" > <template v-for="(note, i) in notes" :key="note.id"> - <DynamicNote :class="$style.note" :note="note" :withHardMute="true"/> - <div v-if="note._shouldInsertAd_" :class="$style.ad"> - <MkAd :preferForms="['horizontal', 'horizontal-big']"/> + <div v-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]"> + <DynamicNote :class="$style.note" :note="note" :withHardMute="true"/> + <div :class="$style.ad"> + <MkAd :preferForms="['horizontal', 'horizontal-big']"/> + </div> </div> + <DynamicNote :class="$style.note" :note="note" :withHardMute="true"/> </template> </component> </template> diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index 81cbd6b842..6097848518 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -159,12 +159,13 @@ function onMousedown(ev: MouseEvent | TouchEvent) { const onDrag = (ev: MouseEvent | TouchEvent) => { ev.preventDefault(); + let beforeValue = finalValue.value; const containerRect = containerEl.value!.getBoundingClientRect(); const pointerX = 'touches' in ev && ev.touches.length > 0 ? ev.touches[0].clientX : 'clientX' in ev ? ev.clientX : 0; const pointerPositionOnContainer = pointerX - (containerRect.left + (thumbWidth / 2)); rawValue.value = Math.min(1, Math.max(0, pointerPositionOnContainer / (containerEl.value!.offsetWidth - thumbWidth))); - if (props.continuousUpdate) { + if (props.continuousUpdate && beforeValue !== finalValue.value) { emit('update:modelValue', finalValue.value); } }; @@ -286,7 +287,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) { border-radius: var(--MI-radius-ellipse); &:hover { - background: var(--MI_THEME-accentLighten); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); } } } diff --git a/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue b/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue index 873b276b3d..dc9bacf481 100644 --- a/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue +++ b/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue @@ -125,7 +125,7 @@ async function done() { left: 0; padding: 12px; border-top: solid 0.5px var(--MI_THEME-divider); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); } diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue index 5f77dc6734..fd56e4902c 100644 --- a/packages/frontend/src/components/MkRoleSelectDialog.vue +++ b/packages/frontend/src/components/MkRoleSelectDialog.vue @@ -144,7 +144,7 @@ fetchRoles(); } .roleItemArea { - background-color: var(--MI_THEME-acrylicBg); + background-color: color(from var(--MI_THEME-bg) srgb r g b / 0.5); border-radius: var(--MI-radius); padding: 12px; overflow-y: auto; diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue index f122da7468..60c99880cd 100644 --- a/packages/frontend/src/components/MkSigninDialog.vue +++ b/packages/frontend/src/components/MkSigninDialog.vue @@ -84,7 +84,7 @@ function onLogin(res: Misskey.entities.SigninFlowResponse & { finished: true }) align-items: center; font-weight: bold; backdrop-filter: var(--MI-blur, blur(15px)); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); z-index: 1; } diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue index f819f82923..a3828ed1c5 100644 --- a/packages/frontend/src/components/MkSystemWebhookEditor.vue +++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue @@ -280,7 +280,7 @@ onMounted(async () => { left: 0; padding: 12px; border-top: solid 0.5px var(--MI_THEME-divider); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); } diff --git a/packages/frontend/src/components/MkThemePreview.vue b/packages/frontend/src/components/MkThemePreview.vue index 5b180b3680..fb5e2b6571 100644 --- a/packages/frontend/src/components/MkThemePreview.vue +++ b/packages/frontend/src/components/MkThemePreview.vue @@ -4,50 +4,46 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<svg - version="1.1" - viewBox="0 0 203.2 152.4" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" -> +<svg viewBox="0 0 200 150"> <g fill-rule="evenodd"> - <rect width="203.2" height="152.4" :fill="themeVariables.bg" stroke-width=".26458" /> - <rect width="65.498" height="152.4" :fill="themeVariables.panel" stroke-width=".26458" /> - <rect x="65.498" width="137.7" height="40.892" :fill="themeVariables.acrylicBg" stroke-width=".265" /> - <path transform="scale(.26458)" d="m439.77 247.19c-43.673 0-78.832 35.157-78.832 78.83v249.98h407.06v-328.81z" :fill="themeVariables.panel" /> + <rect width="200" height="150" :fill="themeVariables.bg"/> + <rect width="64" height="150" :fill="themeVariables.navBg"/> + <rect x="64" width="136" height="41" :fill="themeVariables.bg"/> + <path transform="scale(.26458)" d="m439.77 247.19c-43.673 0-78.832 35.157-78.832 78.83v249.98h407.06v-328.81z" :fill="themeVariables.panel"/> </g> - <circle cx="32.749" cy="83.054" r="21.132" :fill="themeVariables.accentedBg" stroke-dasharray="0.319256, 0.319256" stroke-width=".15963" style="paint-order:stroke fill markers" /> - <circle cx="136.67" cy="106.76" r="23.876" :fill="themeVariables.fg" fill-opacity="0.5" stroke-dasharray="0.352425, 0.352425" stroke-width=".17621" style="paint-order:stroke fill markers" /> - <g :fill="themeVariables.fg" fill-rule="evenodd" stroke-width=".26458"> - <rect x="171.27" y="87.815" width="48.576" height="6.8747" ry="3.4373"/> - <rect x="171.27" y="105.09" width="48.576" height="6.875" ry="3.4375"/> - <rect x="171.27" y="121.28" width="48.576" height="6.875" ry="3.4375"/> - <rect x="171.27" y="137.47" width="48.576" height="6.875" ry="3.4375"/> + <circle cx="32" cy="83" r="21" :fill="themeVariables.accentedBg"/> + <circle cx="136" cy="106" r="23" :fill="themeVariables.fg" fill-opacity="0.5"/> + <g :fill="themeVariables.fg" fill-rule="evenodd"> + <rect x="171" y="88" width="48" height="6" ry="3"/> + <rect x="171" y="108" width="48" height="6" ry="3"/> + <rect x="171" y="128" width="48" height="6" ry="3"/> </g> - <path d="m65.498 40.892h137.7" :stroke="themeVariables.divider" stroke-width="0.75" /> + <path d="m65.498 40.892h137.7" :stroke="themeVariables.divider" stroke-width="0.75"/> <g transform="matrix(.60823 0 0 .60823 25.45 75.755)" fill="none" :stroke="themeVariables.accent" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"> - <path d="m0 0h24v24h-24z" fill="none" stroke="none" /> - <path d="m5 12h-2l9-9 9 9h-2" /> - <path d="m5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7" /> - <path d="m9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6" /> + <path d="m0 0h24v24h-24z" fill="none" stroke="none"/> + <path d="m5 12h-2l9-9 9 9h-2"/> + <path d="m5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7"/> + <path d="m9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6"/> </g> <g transform="matrix(.61621 0 0 .61621 25.354 117.92)" fill="none" :stroke="themeVariables.fg" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"> - <path d="m0 0h24v24h-24z" fill="none" stroke="none" /> - <path d="m10 5a2 2 0 1 1 4 0 7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6" /> - <path d="m9 17v1a3 3 0 0 0 6 0v-1" /> + <path d="m0 0h24v24h-24z" fill="none" stroke="none"/> + <path d="m10 5a2 2 0 1 1 4 0 7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6"/> + <path d="m9 17v1a3 3 0 0 0 6 0v-1"/> </g> - <image x="20.948" y="18.388" width="23.602" height="23.602" image-rendering="optimizeSpeed" preserveAspectRatio="xMidYMid meet" v-bind="{ 'xlink:href': instance.iconUrl || '/favicon.ico' }" /> + <circle cx="32" cy="32" r="16" :fill="themeVariables.accent"/> + <circle cx="140" cy="20" r="6" :fill="themeVariables.success"/> + <circle cx="160" cy="20" r="6" :fill="themeVariables.warn"/> + <circle cx="180" cy="20" r="6" :fill="themeVariables.error"/> </svg> </template> <script setup lang="ts"> import { ref, watch } from 'vue'; -import { instance } from '@/instance.js'; -import { compile } from '@/theme.js'; -import type { Theme } from '@/theme.js'; -import { deepClone } from '@/utility/clone.js'; import lightTheme from '@@/themes/_light.json5'; import darkTheme from '@@/themes/_dark.json5'; +import type { Theme } from '@/theme.js'; +import { compile } from '@/theme.js'; +import { deepClone } from '@/utility/clone.js'; const props = defineProps<{ theme: Theme; @@ -55,20 +51,23 @@ const props = defineProps<{ const themeVariables = ref<{ bg: string; - acrylicBg: string; panel: string; fg: string; divider: string; accent: string; accentedBg: string; + navBg: string; }>({ bg: 'var(--MI_THEME-bg)', - acrylicBg: 'var(--MI_THEME-acrylicBg)', panel: 'var(--MI_THEME-panel)', fg: 'var(--MI_THEME-fg)', divider: 'var(--MI_THEME-divider)', accent: 'var(--MI_THEME-accent)', accentedBg: 'var(--MI_THEME-accentedBg)', + navBg: 'var(--MI_THEME-navBg)', + success: 'var(--MI_THEME-success)', + warn: 'var(--MI_THEME-warn)', + error: 'var(--MI_THEME-error)', }); watch(() => props.theme, (theme) => { @@ -76,7 +75,7 @@ watch(() => props.theme, (theme) => { const _theme = deepClone(theme); - if (_theme?.base != null) { + if (_theme.base != null) { const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); if (base) _theme.props = Object.assign({}, base.props, _theme.props); } @@ -85,12 +84,15 @@ watch(() => props.theme, (theme) => { themeVariables.value = { bg: compiled.bg ?? 'var(--MI_THEME-bg)', - acrylicBg: compiled.acrylicBg ?? 'var(--MI_THEME-acrylicBg)', panel: compiled.panel ?? 'var(--MI_THEME-panel)', fg: compiled.fg ?? 'var(--MI_THEME-fg)', divider: compiled.divider ?? 'var(--MI_THEME-divider)', accent: compiled.accent ?? 'var(--MI_THEME-accent)', accentedBg: compiled.accentedBg ?? 'var(--MI_THEME-accentedBg)', + navBg: compiled.navBg ?? 'var(--MI_THEME-navBg)', + success: compiled.success ?? 'var(--MI_THEME-success)', + warn: compiled.warn ?? 'var(--MI_THEME-warn)', + error: compiled.error ?? 'var(--MI_THEME-error)', }; }, { immediate: true }); </script> diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 1dda723fd2..06816e75da 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -80,7 +80,7 @@ const emit = defineEmits<{ const displayBackButton = props.displayBackButton && history.state.key !== 'index' && history.length > 1 && inject('shouldBackButton', true); -const viewId = inject(DI.viewId); +//const viewId = inject(DI.viewId); const injectedPageMetadata = inject(DI.pageMetadata); const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value); diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue index 7c8a5d64d7..55de0df690 100644 --- a/packages/frontend/src/components/grid/MkDataCell.vue +++ b/packages/frontend/src/components/grid/MkDataCell.vue @@ -345,7 +345,7 @@ $cellHeight: 28px; border: solid 0.5px transparent; &.selected { - border: solid 0.5px var(--MI_THEME-accentLighten); + border: solid 0.5px hsl(from var(--MI_THEME-accent) h s calc(l + 10)); } &.ranged { diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue index a56a24ff7d..10925fa4ab 100644 --- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue +++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue @@ -296,7 +296,7 @@ onMounted(async () => { left: 0; padding: 12px; border-top: solid 0.5px var(--MI_THEME-divider); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); } diff --git a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue index 12fd867407..5a5e305f80 100644 --- a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue +++ b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue @@ -213,7 +213,7 @@ async function del() { left: 0; padding: 12px; border-top: solid 0.5px var(--MI_THEME-divider); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); } diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index f08c9c8916..e9357577ff 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -304,7 +304,7 @@ definePage(() => ({ .footer { -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); border-top: solid 0.5px var(--MI_THEME-divider); } diff --git a/packages/frontend/src/pages/chat/XMessage.vue b/packages/frontend/src/pages/chat/XMessage.vue index ab57020613..33741b1845 100644 --- a/packages/frontend/src/pages/chat/XMessage.vue +++ b/packages/frontend/src/pages/chat/XMessage.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="[$style.root, { [$style.isMe]: isMe }]"> <MkAvatar :class="$style.avatar" :user="message.fromUser" :link="!isMe" :preview="false"/> <div :class="$style.body" @contextmenu.stop="onContextmenu"> - <div v-if="!isMe && prefer.s['chat.showSenderName']" :class="$style.header"><MkUserName :user="message.fromUser"/></div> + <div :class="$style.header"><MkUserName v-if="!isMe && prefer.s['chat.showSenderName']" :user="message.fromUser"/></div> <MkFukidashi :class="$style.fukidashi" :tail="isMe ? 'right' : 'left'" :accented="isMe"> <div v-if="!message.isDeleted" :class="$style.content"> <Mfm @@ -216,10 +216,6 @@ function showMenu(ev: MouseEvent, contextmenu = false) { flex-direction: row-reverse; text-align: right; - .content { - color: var(--MI_THEME-fgOnAccent); - } - .footer { flex-direction: row-reverse; } @@ -230,8 +226,27 @@ function showMenu(ev: MouseEvent, contextmenu = false) { position: sticky; top: calc(16px + var(--MI-stickyTop, 0px)); display: block; - width: 52px; - height: 52px; + width: 50px; + height: 50px; +} + +@container (max-width: 450px) { + .root { + &.isMe { + .avatar { + display: none; + } + } + } + + .avatar { + width: 42px; + height: 42px; + } + + .fukidashi { + font-size: 90%; + } } .body { @@ -239,6 +254,7 @@ function showMenu(ev: MouseEvent, contextmenu = false) { } .header { + min-height: 4px; // fukidashiの位置調整も兼ねるため font-size: 80%; } @@ -252,9 +268,6 @@ function showMenu(ev: MouseEvent, contextmenu = false) { word-break: break-word; } -.file { -} - .footer { display: flex; flex-direction: row; diff --git a/packages/frontend/src/pages/chat/home.home.vue b/packages/frontend/src/pages/chat/home.home.vue index 49857db9ab..105f5f7989 100644 --- a/packages/frontend/src/pages/chat/home.home.vue +++ b/packages/frontend/src/pages/chat/home.home.vue @@ -119,7 +119,8 @@ function start(ev: MouseEvent) { } async function startUser() { - os.selectUser().then(user => { + // TODO: localOnly は連合に対応したら消す + os.selectUser({ localOnly: true }).then(user => { router.push(`/chat/user/${user.id}`); }); } diff --git a/packages/frontend/src/pages/chat/room.info.vue b/packages/frontend/src/pages/chat/room.info.vue index 7e10336fd3..8439e5f772 100644 --- a/packages/frontend/src/pages/chat/room.info.vue +++ b/packages/frontend/src/pages/chat/room.info.vue @@ -63,11 +63,11 @@ function save() { async function del() { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.ts.areYouSure, + text: i18n.tsx.deleteAreYouSure({ x: name_.value }), }); if (canceled) return; - misskeyApi('chat/rooms/delete', { + await os.apiWithDialog('chat/rooms/delete', { roomId: props.room.id, }); router.push('/chat'); @@ -81,10 +81,6 @@ watch(isMuted, async () => { mute: isMuted.value, }); }); - -onMounted(async () => { - -}); </script> <style lang="scss" module> diff --git a/packages/frontend/src/pages/chat/room.members.vue b/packages/frontend/src/pages/chat/room.members.vue index 2b31efab38..bff038570f 100644 --- a/packages/frontend/src/pages/chat/room.members.vue +++ b/packages/frontend/src/pages/chat/room.members.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkUserCardMini :user="room.owner"/> </MkA> - <hr> + <hr v-if="memberships.length > 0"> <div v-for="membership in memberships" :key="membership.id" :class="$style.membership"> <MkA :class="$style.membershipBody" :to="`${userPage(membership.user)}`"> diff --git a/packages/frontend/src/pages/chat/room.search.vue b/packages/frontend/src/pages/chat/room.search.vue index de5e7156ca..e382834578 100644 --- a/packages/frontend/src/pages/chat/room.search.vue +++ b/packages/frontend/src/pages/chat/room.search.vue @@ -9,20 +9,25 @@ SPDX-License-Identifier: AGPL-3.0-only v-model="searchQuery" :placeholder="i18n.ts._chat.searchMessages" type="search" + @enter="search()" > <template #prefix><i class="ti ti-search"></i></template> </MkInput> - <MkButton v-if="searchQuery.length > 0" primary rounded @click="search">{{ i18n.ts.search }}</MkButton> + <MkButton primary rounded @click="search">{{ i18n.ts.search }}</MkButton> <MkFoldableSection v-if="searched"> <template #header>{{ i18n.ts.searchResult }}</template> - <div class="_gaps_s"> + <div v-if="searchResults.length > 0" class="_gaps_s"> <div v-for="message in searchResults" :key="message.id" :class="$style.searchResultItem"> <XMessage :message="message" :user="message.fromUser" :isSearchResult="true"/> </div> </div> + <div v-else class="_fullinfo"> + <img :src="infoImageUrl" draggable="false"/> + <div>{{ i18n.ts.notFound }}</div> + </div> </MkFoldableSection> </div> </template> @@ -33,6 +38,7 @@ import * as Misskey from 'misskey-js'; import XMessage from './XMessage.vue'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; +import { infoImageUrl } from '@/instance.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import * as os from '@/os.js'; import MkInput from '@/components/MkInput.vue'; diff --git a/packages/frontend/src/pages/chat/room.vue b/packages/frontend/src/pages/chat/room.vue index ec92a1dce1..ce823968f7 100644 --- a/packages/frontend/src/pages/chat/room.vue +++ b/packages/frontend/src/pages/chat/room.vue @@ -67,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only <Transition name="fade"> <div v-show="showIndicator" :class="$style.new"> <button class="_buttonPrimary" :class="$style.newButton" @click="onIndicatorClick"> - <i class="fas ti-fw fa-arrow-circle-down" :class="$style.newIcon"></i>{{ i18n.ts.newMessageExists }} + <i class="fas ti-fw fa-arrow-circle-down" :class="$style.newIcon"></i>{{ i18n.ts._chat.newMessage }} </button> </div> </Transition> @@ -391,6 +391,7 @@ const headerActions = computed(() => [{ definePage(computed(() => !initializing.value ? user.value ? { userName: user, + title: user.value.name ?? user.value.username, avatar: user, } : { title: room.value?.name, diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index f9d3197ece..dc570b05e4 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -245,7 +245,7 @@ async function del() { left: 0; padding: 12px; border-top: solid 0.5px var(--MI_THEME-divider); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); } diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index c2f66c0e4d..825a3be7c1 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -467,7 +467,7 @@ definePage(() => ({ <style lang="scss" module> .footer { backdrop-filter: var(--MI-blur, blur(15px)); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); border-top: solid .5px var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue index d2720a79fc..957b1cfc3d 100644 --- a/packages/frontend/src/pages/reversi/game.setting.vue +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -292,7 +292,7 @@ onUnmounted(() => { .footer { -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); border-top: solid 0.5px var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue index 9b2b40374e..902e058e0d 100644 --- a/packages/frontend/src/pages/settings/deck.vue +++ b/packages/frontend/src/pages/settings/deck.vue @@ -45,6 +45,35 @@ SPDX-License-Identifier: AGPL-3.0-only </MkRadios> </MkPreferenceContainer> </SearchMarker> + + <SearchMarker :keywords="['menu', 'position']"> + <MkPreferenceContainer k="deck.menuPosition"> + <MkRadios v-model="menuPosition"> + <template #label><SearchLabel>{{ i18n.ts._deck.deckMenuPosition }}</SearchLabel></template> + <option value="right">{{ i18n.ts.right }}</option> + <option value="bottom">{{ i18n.ts.bottom }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['navbar', 'position']"> + <MkPreferenceContainer k="deck.navbarPosition"> + <MkRadios v-model="navbarPosition"> + <template #label><SearchLabel>{{ i18n.ts._deck.navbarPosition }}</SearchLabel></template> + <option value="left">{{ i18n.ts.left }}</option> + <option value="top">{{ i18n.ts.top }}</option> + <option value="bottom">{{ i18n.ts.bottom }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['column', 'gap', 'margin']"> + <MkPreferenceContainer k="deck.columnGap"> + <MkRange v-model="columnGap" :min="3" :max="100" :step="1" :continuousUpdate="true"> + <template #label><SearchLabel>{{ i18n.ts._deck.columnGap }}</SearchLabel></template> + </MkRange> + </MkPreferenceContainer> + </SearchMarker> </div> </SearchMarker> </template> @@ -53,6 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkRadios from '@/components/MkRadios.vue'; +import MkRange from '@/components/MkRange.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { prefer } from '@/preferences.js'; @@ -62,6 +92,9 @@ const navWindow = prefer.model('deck.navWindow'); const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages'); const alwaysShowMainColumn = prefer.model('deck.alwaysShowMainColumn'); const columnAlign = prefer.model('deck.columnAlign'); +const columnGap = prefer.model('deck.columnGap'); +const menuPosition = prefer.model('deck.menuPosition'); +const navbarPosition = prefer.model('deck.navbarPosition'); const profilesSyncEnabled = ref(prefer.isSyncEnabled('deck.profiles')); diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue index c68c04bb44..fcd0b293e0 100644 --- a/packages/frontend/src/pages/settings/theme.manage.vue +++ b/packages/frontend/src/pages/settings/theme.manage.vue @@ -38,14 +38,13 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkInput from '@/components/MkInput.vue'; import MkButton from '@/components/MkButton.vue'; -import { getBuiltinThemesRef } from '@/theme.js'; +import { getBuiltinThemesRef, getThemesRef, removeTheme } from '@/theme.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import * as os from '@/os.js'; -import { getThemes, removeTheme } from '@/theme-store.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -const installedThemes = ref(getThemes()); +const installedThemes = getThemesRef(); const builtinThemes = getBuiltinThemesRef(); const selectedThemeId = ref<string | null>(null); diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index b0c0f0b5bb..8976d104c7 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -210,20 +210,19 @@ import FormLink from '@/components/form/link.vue'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkThemePreview from '@/components/MkThemePreview.vue'; -import { getBuiltinThemesRef } from '@/theme.js'; +import { getBuiltinThemesRef, getThemesRef } from '@/theme.js'; import { selectFile } from '@/utility/select-file.js'; import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js'; import { store } from '@/store.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { uniqueBy } from '@/utility/array.js'; -import { getThemes } from '@/theme-store.js'; import { definePage } from '@/page.js'; import { miLocalStorage } from '@/local-storage.js'; import { reloadAsk } from '@/utility/reload-ask.js'; import { prefer } from '@/preferences.js'; -const installedThemes = ref(getThemes()); +const installedThemes = getThemesRef(); const builtinThemes = getBuiltinThemesRef(); const instanceDarkTheme = computed<Theme | null>(() => instance.defaultDarkTheme ? JSON5.parse(instance.defaultDarkTheme) : null); @@ -281,10 +280,6 @@ watch(wallpaper, async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); -onActivated(() => { - installedThemes.value = getThemes(); -}); - function setWallpaper(event) { selectFile(event.currentTarget ?? event.target, null).then(file => { wallpaper.value = file.url; diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index f0f7390d2c..9b789e0d5b 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -77,7 +77,7 @@ definePage(() => ({ .footer { -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); - background: var(--MI_THEME-acrylicBg); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5); border-top: solid 0.5px var(--MI_THEME-divider); display: flex; } diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue index d3c37a32b6..2570c40fe0 100644 --- a/packages/frontend/src/pages/theme-editor.vue +++ b/packages/frontend/src/pages/theme-editor.vue @@ -86,10 +86,9 @@ import MkCodeEditor from '@/components/MkCodeEditor.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkFolder from '@/components/MkFolder.vue'; import { $i } from '@/i.js'; -import { applyTheme } from '@/theme.js'; +import { addTheme, applyTheme } from '@/theme.js'; import * as os from '@/os.js'; import { store } from '@/store.js'; -import { addTheme } from '@/theme-store.js'; import { i18n } from '@/i18n.js'; import { useLeaveGuard } from '@/use/use-leave-guard.js'; import { definePage } from '@/page.js'; diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue index b95f79f42d..16d9af1f0c 100644 --- a/packages/frontend/src/pages/welcome.entrance.a.vue +++ b/packages/frontend/src/pages/welcome.entrance.a.vue @@ -151,7 +151,7 @@ misskeyApiGet('federation/instances', { left: 0; right: 0; margin: auto; - background: var(--MI_THEME-acrylicPanel); + background: color(from var(--MI_THEME-panel) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); border-radius: var(--MI-radius-ellipse); diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 2ed3b52b2a..bccc09921c 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -420,7 +420,16 @@ export const PREF_DEF = { default: true, }, 'deck.columnAlign': { - default: 'left' as 'left' | 'right' | 'center', + default: 'center' as 'left' | 'right' | 'center', + }, + 'deck.columnGap': { + default: 6, + }, + 'deck.menuPosition': { + default: 'bottom' as 'right' | 'bottom', + }, + 'deck.navbarPosition': { + default: 'left' as 'left' | 'top' | 'bottom', }, 'chat.showSenderName': { diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 9e928028fd..a944c0e60b 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -33,5 +33,5 @@ mainRouter.addListener('replace', ctx => { mainRouter.init(); export function useRouter(): Router { - return inject(DI.router) ?? mainRouter; + return inject(DI.router, null) ?? mainRouter; } diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index dce5e0371e..d32f628f07 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -432,7 +432,7 @@ rt { } ._acrylic { - background: var(--MI_THEME-acrylicPanel); + background: color(from var(--MI_THEME-panel) srgb r g b / 0.5); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); } diff --git a/packages/frontend/src/theme-store.ts b/packages/frontend/src/theme-store.ts deleted file mode 100644 index 2ae5d8730e..0000000000 --- a/packages/frontend/src/theme-store.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type { Theme } from '@/theme.js'; -import { getBuiltinThemes } from '@/theme.js'; -import { $i } from '@/i.js'; -import { prefer } from '@/preferences.js'; - -export function getThemes(): Theme[] { - if ($i == null) return []; - return prefer.s.themes; -} - -export async function addTheme(theme: Theme): Promise<void> { - if ($i == null) return; - const builtinThemes = await getBuiltinThemes(); - if (builtinThemes.some(t => t.id === theme.id)) { - throw new Error('builtin theme'); - } - const themes = getThemes(); - if (themes.some(t => t.id === theme.id)) { - throw new Error('already exists'); - } - prefer.commit('themes', [...themes, theme]); -} - -export async function removeTheme(theme: Theme): Promise<void> { - if ($i == null) return; - const themes = getThemes().filter(t => t.id !== theme.id); - prefer.commit('themes', themes); -} diff --git a/packages/frontend/src/theme.ts b/packages/frontend/src/theme.ts index 4f61ab6e0e..c72c610d57 100644 --- a/packages/frontend/src/theme.ts +++ b/packages/frontend/src/theme.ts @@ -8,11 +8,13 @@ import tinycolor from 'tinycolor2'; import lightTheme from '@@/themes/_light.json5'; import darkTheme from '@@/themes/_dark.json5'; import JSON5 from 'json5'; +import type { Ref } from 'vue'; import type { BundledTheme } from 'shiki/themes'; import { deepClone } from '@/utility/clone.js'; import { globalEvents } from '@/events.js'; import { miLocalStorage } from '@/local-storage.js'; -import { addTheme, getThemes } from '@/theme-store.js'; +import { $i } from '@/i.js'; +import { prefer } from '@/preferences.js'; export type Theme = { id: string; @@ -59,11 +61,34 @@ export const getBuiltinThemes = () => Promise.all( ].map(name => import(`@@/themes/${name}.json5`).then(({ default: _default }): Theme => _default)), ); -export const getBuiltinThemesRef = () => { +export function getBuiltinThemesRef() { const builtinThemes = ref<Theme[]>([]); getBuiltinThemes().then(themes => builtinThemes.value = themes); return builtinThemes; -}; +} + +export function getThemesRef(): Ref<Theme[]> { + return prefer.r.themes; +} + +export async function addTheme(theme: Theme): Promise<void> { + if ($i == null) return; + const builtinThemes = await getBuiltinThemes(); + if (builtinThemes.some(t => t.id === theme.id)) { + throw new Error('builtin theme'); + } + const themes = prefer.s.themes; + if (themes.some(t => t.id === theme.id)) { + throw new Error('already exists'); + } + prefer.commit('themes', [...themes, theme]); +} + +export async function removeTheme(theme: Theme): Promise<void> { + if ($i == null) return; + const themes = prefer.s.themes.filter(t => t.id !== theme.id); + prefer.commit('themes', themes); +} const themeFontFaceName = 'sharkey-theme-font-face'; @@ -206,7 +231,7 @@ export function parseThemeCode(code: string): Theme { if (!validateTheme(theme)) { throw new Error('This theme is invaild'); } - if (getThemes().some(t => t.id === theme.id)) { + if (prefer.s.themes.some(t => t.id === theme.id)) { throw new Error('This theme is already installed'); } diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue index 18d9f2f5f8..cbfe475627 100644 --- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue @@ -166,7 +166,7 @@ function more() { &:hover, &.active { &::before { - background: var(--MI_THEME-accentLighten); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); } } } diff --git a/packages/frontend/src/ui/_common_/navbar-h.vue b/packages/frontend/src/ui/_common_/navbar-h.vue new file mode 100644 index 0000000000..c93935dd26 --- /dev/null +++ b/packages/frontend/src/ui/_common_/navbar-h.vue @@ -0,0 +1,214 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div class="azykntjl"> + <div class="body"> + <div class="left"> + <button v-click-anime class="item _button instance" @click="openInstanceMenu"> + <img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/> + </button> + <MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact> + <i class="ti ti-home ti-fw"></i> + </MkA> + <template v-for="item in menu"> + <div v-if="item === '-'" class="divider"></div> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <i class="ti-fw" :class="navbarItemDef[item].icon"></i> + <span v-if="navbarItemDef[item].indicated" class="indicator _blink"><i class="_indicatorCircle"></i></span> + </component> + </template> + <div class="divider"></div> + <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null"> + <i class="ti ti-dashboard ti-fw"></i> + </MkA> + <button v-click-anime class="item _button" @click="more"> + <i class="ti ti-dots ti-fw"></i> + <span v-if="otherNavItemIndicated" class="indicator _blink"><i class="_indicatorCircle"></i></span> + </button> + </div> + <div class="right"> + <MkA v-click-anime v-tooltip="i18n.ts.settings" class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null"> + <i class="ti ti-settings ti-fw"></i> + </MkA> + <button v-click-anime class="item _button account" @click="openAccountMenu"> + <MkAvatar :user="$i" class="avatar"/><MkAcct class="acct" :user="$i"/> + </button> + <div class="post" @click="os.post()"> + <MkButton class="button" gradate full rounded> + <i class="ti ti-pencil ti-fw"></i> + </MkButton> + </div> + </div> + </div> +</div> +</template> + +<script lang="ts" setup> +import { computed, defineAsyncComponent, onMounted, ref } from 'vue'; +import { openInstanceMenu } from './common.js'; +import * as os from '@/os.js'; +import { navbarItemDef } from '@/navbar.js'; +import MkButton from '@/components/MkButton.vue'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; +import { prefer } from '@/preferences.js'; +import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; +import { $i } from '@/i.js'; + +const WINDOW_THRESHOLD = 1400; + +const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD); +const menu = ref(prefer.s.menu); +// const menuDisplay = computed(store.makeGetterSetter('menuDisplay')); +const otherNavItemIndicated = computed<boolean>(() => { + for (const def in navbarItemDef) { + if (menu.value.includes(def)) continue; + if (navbarItemDef[def].indicated) return true; + } + return false; +}); + +function more(ev: MouseEvent) { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), { + src: ev.currentTarget ?? ev.target, + anchor: { x: 'center', y: 'bottom' }, + }, { + closed: () => dispose(), + }); +} + +function openAccountMenu(ev: MouseEvent) { + openAccountMenu_({ + withExtraOperation: true, + }, ev); +} + +onMounted(() => { + window.addEventListener('resize', () => { + settingsWindowed.value = (window.innerWidth >= WINDOW_THRESHOLD); + }, { passive: true }); +}); + +</script> + +<style lang="scss" scoped> +.azykntjl { + $height: 60px; + $avatar-size: 32px; + $avatar-margin: 8px; + + position: sticky; + top: 0; + z-index: 1000; + width: 100%; + height: $height; + background: color(from var(--MI_THEME-bg) srgb r g b / 0.75); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + + > .body { + max-width: 1380px; + margin: 0 auto; + display: flex; + + > .right, + > .left { + + > .item { + position: relative; + font-size: 0.9em; + display: inline-block; + padding: 0 12px; + line-height: $height; + + > i, + > .avatar { + margin-right: 0; + } + + > i { + left: 10px; + } + + > .avatar { + width: $avatar-size; + height: $avatar-size; + vertical-align: middle; + } + + > .indicator { + position: absolute; + top: 0; + left: 0; + color: var(--MI_THEME-navIndicator); + font-size: 8px; + } + + &:hover { + text-decoration: none; + color: var(--MI_THEME-navHoverFg); + } + + &.active { + color: var(--MI_THEME-navActive); + } + } + + > .divider { + display: inline-block; + height: 16px; + margin: 0 10px; + border-right: solid 0.5px var(--MI_THEME-divider); + } + + > .instance { + display: inline-block; + position: relative; + width: 56px; + height: 100%; + vertical-align: bottom; + + > img { + display: inline-block; + width: 24px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; + } + } + + > .post { + display: inline-block; + + > .button { + width: 40px; + height: 40px; + padding: 0; + min-width: 0; + } + } + + > .account { + display: inline-flex; + align-items: center; + vertical-align: top; + margin-right: 8px; + + > .acct { + margin-left: 8px; + } + } + } + + > .right { + margin-left: auto; + } + } +} +</style> diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index db5ba75b2a..64763fee52 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -389,7 +389,7 @@ function menuEdit() { &:hover, &.active { &::before { - background: var(--MI_THEME-accentLighten); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); } } } @@ -619,7 +619,7 @@ function menuEdit() { &:hover, &.active { &::before { - background: var(--MI_THEME-accentLighten); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); } } } diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index a59399a10c..a03d34da9d 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -4,42 +4,47 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="[$style.root, { [$style.rootIsMobile]: isMobile }]"> - <XSidebar v-if="!isMobile"/> +<div :class="[$style.root, { [$style.withWallpaper]: withWallpaper }]"> + <XSidebar v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'left'"/> <div :class="$style.main"> + <XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'"/> + <XAnnouncements v-if="$i"/> <XStatusBars/> - <div ref="columnsEl" :class="[$style.sections, { [$style.center]: prefer.r['deck.columnAlign'].value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.self="onWheel"> - <!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため --> - <section - v-for="ids in layout" - :class="$style.section" - :style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }" - @wheel.self="onWheel" - > - <Suspense> - <component - :is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn" - v-for="id in ids" - :ref="id" - :key="id" - :class="$style.column" - :column="columns.find(c => c.id === id)!" - :isStacked="ids.length > 1" - @headerWheel="onWheel" - /> - <template #fallback> - <MkLoading/> - </template> - </Suspense> - </section> - <div v-if="layout.length === 0" class="_panel" :class="$style.onboarding"> - <div>{{ i18n.ts._deck.introduction }}</div> - <MkButton primary style="margin: 1em auto;" @click="addColumn">{{ i18n.ts._deck.addColumn }}</MkButton> - <div>{{ i18n.ts._deck.introduction2 }}</div> + + <div :class="$style.columnsWrapper"> + <div ref="columnsEl" :class="[$style.columns, { [$style.center]: prefer.r['deck.columnAlign'].value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.self="onWheel"> + <!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため --> + <section + v-for="ids in layout" + :class="$style.section" + :style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }" + @wheel.self="onWheel" + > + <Suspense> + <component + :is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn" + v-for="id in ids" + :ref="id" + :key="id" + :class="[$style.column, { '_shadow': withWallpaper }]" + :column="columns.find(c => c.id === id)!" + :isStacked="ids.length > 1" + @headerWheel="onWheel" + /> + <template #fallback> + <MkLoading/> + </template> + </Suspense> + </section> + <div v-if="layout.length === 0" class="_panel" :class="$style.onboarding"> + <div>{{ i18n.ts._deck.introduction }}</div> + <div>{{ i18n.ts._deck.introduction2 }}</div> + </div> </div> - <div :class="$style.sideMenu"> + + <div v-if="prefer.r['deck.menuPosition'].value === 'right'" :class="$style.sideMenu"> <div :class="$style.sideMenuTop"> <button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button> <button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button> @@ -48,22 +53,37 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.sideMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button> </div> <div :class="$style.sideMenuBottom"> - <button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.sideMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings"></i></button> + <button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.sideMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button> </div> </div> </div> - </div> - <div v-if="isMobile" :class="$style.nav"> - <button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button> - <button :class="$style.navButton" class="_button" @click="mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button> - <button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"> - <i :class="$style.navButtonIcon" class="ti ti-bell"></i> - <span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator" class="_blink"> - <span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span> - </span> - </button> - <button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button> + <div v-if="prefer.r['deck.menuPosition'].value === 'bottom'" :class="$style.bottomMenu"> + <div :class="$style.bottomMenuLeft"> + <button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.bottomMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button> + <button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.bottomMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button> + </div> + <div :class="$style.bottomMenuMiddle"> + <button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.bottomMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button> + </div> + <div :class="$style.bottomMenuRight"> + <button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.bottomMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button> + </div> + </div> + + <XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'bottom'"/> + + <div v-if="isMobile" :class="$style.nav"> + <button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button> + <button :class="$style.navButton" class="_button" @click="mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button> + <button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"> + <i :class="$style.navButtonIcon" class="ti ti-bell"></i> + <span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator" class="_blink"> + <span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span> + </span> + </button> + <button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button> + </div> </div> <Transition @@ -97,10 +117,11 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, ref, useTemplateRef } from 'vue'; +import { computed, defineAsyncComponent, ref, useTemplateRef, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import XCommon from './_common_/common.vue'; import XSidebar from '@/ui/_common_/navbar.vue'; +import XNavbarH from '@/ui/_common_/navbar-h.vue'; import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; @@ -122,6 +143,8 @@ import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue'; import XFollowingColumn from '@/ui/deck/following-column.vue'; import { mainRouter } from '@/router.js'; import { columns, layout, columnTypes, switchProfileMenu, addColumn as addColumnToStore, deleteProfile as deleteProfile_ } from '@/deck.js'; +import { miLocalStorage } from '@/local-storage.js'; + const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue')); @@ -155,7 +178,9 @@ window.addEventListener('resize', () => { }); const snapScroll = deviceKind === 'smartphone' || deviceKind === 'tablet'; +const withWallpaper = miLocalStorage.getItem('wallpaper') != null; const drawerMenuShowing = ref(false); +const gap = prefer.r['deck.columnGap']; /* const route = 'TODO'; @@ -256,16 +281,18 @@ async function deleteProfile() { --MI-margin: var(--MI-marginHalf); - --columnGap: 6px; + --columnGap: v-bind("gap + 'px'"); display: flex; height: 100dvh; box-sizing: border-box; flex: 1; -} -.rootIsMobile { - padding-bottom: 58px; + &.withWallpaper { + .main { + background: transparent; + } + } } .main { @@ -273,15 +300,23 @@ async function deleteProfile() { min-width: 0; display: flex; flex-direction: column; + background: var(--MI_THEME-deckBg); } -.sections { +.columnsWrapper { + flex: 1; + display: flex; + flex-direction: row; +} + +.columns { flex: 1; display: flex; overflow-x: auto; overflow-y: clip; overscroll-behavior: contain; - background: var(--MI_THEME-deckBg); + padding: var(--columnGap); + gap: var(--columnGap); &.center { > .section:first-of-type { @@ -301,15 +336,10 @@ async function deleteProfile() { .section { display: flex; flex-direction: column; - scroll-snap-align: start; flex-shrink: 0; - padding-top: var(--columnGap); - padding-bottom: var(--columnGap); - padding-left: var(--columnGap); - - > .column:not(:last-of-type) { - margin-bottom: var(--columnGap); - } + gap: var(--columnGap); + scroll-snap-align: start; + scroll-margin-left: var(--columnGap); } .onboarding { @@ -348,6 +378,33 @@ async function deleteProfile() { margin-top: auto; } +.bottomMenu { + flex-shrink: 0; + display: flex; + flex-direction: row; + justify-content: center; + height: 32px; +} + +.bottomMenuButton { + display: block; + height: 100%; + aspect-ratio: 1; +} + +.bottomMenuLeft { + margin-right: auto; +} + +.bottomMenuMiddle { + margin-left: auto; + margin-right: auto; +} + +.bottomMenuRight { + margin-left: auto; +} + .menuBg { z-index: 1001; } @@ -367,10 +424,6 @@ async function deleteProfile() { } .nav { - position: fixed; - z-index: 1000; - bottom: 0; - left: 0; padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px; display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index 5bad337461..bb931dda3f 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div - :class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready }]" + :class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready, [$style.withWallpaper]: withWallpaper }]" @dragover.prevent.stop="onDragover" @dragleave="onDragleave" @drop.prevent.stop="onDrop" @@ -48,11 +48,14 @@ import type { MenuItem } from '@/types/menu.js'; import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from '@/deck.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; +import { miLocalStorage } from '@/local-storage.js'; provide('shouldHeaderThin', true); provide('shouldOmitHeaderTitle', true); provide('forceSpacerMin', true); +const withWallpaper = miLocalStorage.getItem('wallpaper') != null; + const props = withDefaults(defineProps<{ column: Column; isStacked?: boolean; @@ -108,9 +111,7 @@ function getMenu() { const menuItems: MenuItem[] = []; if (props.menu) { - menuItems.push(...props.menu, { - type: 'divider', - }); + menuItems.push(...props.menu); } if (props.refresher) { @@ -125,6 +126,12 @@ function getMenu() { }); } + if (menuItems.length > 0) { + menuItems.push({ + type: 'divider', + }); + } + menuItems.push({ icon: 'ti ti-settings', text: i18n.ts._deck.configureColumn, @@ -153,6 +160,21 @@ function getMenu() { }, }); + const flexibleRef = ref(props.column.flexible ?? false); + + watch(flexibleRef, flexible => { + updateColumn(props.column.id, { + flexible, + }); + }); + + menuItems.push({ + type: 'switch', + icon: 'ti ti-arrows-horizontal', + text: i18n.ts._deck.flexible, + ref: flexibleRef, + }); + const moveToMenuItems: MenuItem[] = []; moveToMenuItems.push({ @@ -333,9 +355,7 @@ function onDrop(ev) { } &.naked { - background: var(--MI_THEME-acrylicBg) !important; - -webkit-backdrop-filter: var(--MI-blur, blur(10px)); - backdrop-filter: var(--MI-blur, blur(10px)); + background: color(from var(--MI_THEME-bg) srgb r g b / 0.5) !important; > .header { background: transparent; @@ -353,6 +373,22 @@ function onDrop(ev) { } } + &.withWallpaper { + &.naked { + background: color(from var(--MI_THEME-bg) srgb r g b / 0.75) !important; + -webkit-backdrop-filter: var(--MI-blur, blur(10px)); + backdrop-filter: var(--MI-blur, blur(10px)); + + > .header { + color: light-dark(#000000bf, #ffffffbf); + } + } + + .tabShape { + display: none; + } + } + &.paged { background: var(--MI_THEME-bg) !important; diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts index df621beb7d..1563f19c34 100644 --- a/packages/frontend/src/utility/autogen/settings-search-index.ts +++ b/packages/frontend/src/utility/autogen/settings-search-index.ts @@ -682,7 +682,7 @@ export const searchIndexes: SearchIndexItem[] = [ id: '9bNikHWzQ', children: [ { - id: 'appYJbpkK', + id: 't6XtfnRm9', label: i18n.ts._settings.showNavbarSubButtons, keywords: ['navbar', 'sidebar', 'toggle', 'button', 'sub'], }, @@ -880,6 +880,21 @@ export const searchIndexes: SearchIndexItem[] = [ label: i18n.ts._deck.columnAlign, keywords: ['column', 'align'], }, + { + id: 'gtdEA4FTa', + label: i18n.ts._deck.deckMenuPosition, + keywords: ['menu', 'position'], + }, + { + id: 'DHVFdPBT6', + label: i18n.ts._deck.navbarPosition, + keywords: ['navbar', 'position'], + }, + { + id: '3UQ8rUssZ', + label: i18n.ts._deck.columnGap, + keywords: ['column', 'gap', 'margin'], + }, ], label: i18n.ts.deck, keywords: ['deck', 'ui'], diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index e2a40fad47..245a8c1924 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.3.2-beta.18", + "version": "2025.3.2-beta.20", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", |