Merge branch 'misskey-develop' into merge/2025-03-24

This commit is contained in:
Hazelnoot 2025-03-31 16:07:41 -04:00
commit 2983092c06
59 changed files with 633 additions and 251 deletions

View file

@ -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 全体的なパフォーマンス向上

28
locales/index.d.ts vendored
View file

@ -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;
/**
@ -10245,6 +10257,18 @@ export interface Locale extends ILocale {
*
*/
"columnAlign": string;
/**
*
*/
"columnGap": string;
/**
*
*/
"deckMenuPosition": string;
/**
*
*/
"navbarPosition": string;
/**
*
*/
@ -10298,7 +10322,7 @@ export interface Locale extends ILocale {
*/
"introduction": string;
/**
* +
* +
*/
"introduction2": string;
/**

View file

@ -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: "「幅を自動調整」が有効の場合、これが幅の最小値となります"

View file

@ -1,6 +1,6 @@
{
"name": "sharkey",
"version": "2025.3.2-beta.18",
"version": "2025.3.2-beta.20",
"codename": "shonk",
"repository": {
"type": "git",

View file

@ -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;

View file

@ -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));
}

View file

@ -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)',

View file

@ -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)',

View file

@ -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',

View file

@ -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',

View file

@ -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',

View file

@ -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',

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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 {

View file

@ -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;

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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>

View file

@ -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));
}
}
}

View file

@ -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));
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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));
}

View file

@ -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>

View file

@ -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);

View file

@ -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 {

View file

@ -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));
}

View file

@ -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));
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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}`);
});
}

View file

@ -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>

View file

@ -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)}`">

View file

@ -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';

View file

@ -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,

View file

@ -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));
}

View file

@ -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>

View file

@ -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>

View file

@ -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'));

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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';

View file

@ -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);

View file

@ -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': {

View file

@ -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;
}

View file

@ -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));
}

View file

@ -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);
}

View file

@ -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');
}

View file

@ -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));
}
}
}

View file

@ -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>

View file

@ -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));
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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'],

View file

@ -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",