summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-03-24 21:32:46 +0900
committerGitHub <noreply@github.com>2025-03-24 21:32:46 +0900
commitf1f24e39d2df3135493e2c2087230b428e2d02b7 (patch)
treea5ae0e9d2cf810649b2f4e08ef4d00ce7ea91dc9 /packages/frontend/src/components
parentfix(frontend): fix broken styles (diff)
downloadsharkey-f1f24e39d2df3135493e2c2087230b428e2d02b7.tar.gz
sharkey-f1f24e39d2df3135493e2c2087230b428e2d02b7.tar.bz2
sharkey-f1f24e39d2df3135493e2c2087230b428e2d02b7.zip
Feat: Chat (#15686)
* wip * wip * wip * wip * wip * wip * Update types.ts * Create 1742203321812-chat.js * wip * wip * Update room.vue * Update home.vue * Update home.vue * Update ja-JP.yml * Update index.d.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update CHANGELOG.md * wip * Update home.vue * clean up * Update misskey-js.api.md * wip * wip * wip * wip * wip * wip * wip * wip * wip * lint fixes * lint * Update UserEntityService.ts * search * wip * 🎨 * wip * Update home.ownedRooms.vue * wip * Update CHANGELOG.md * Update style.scss * wip * improve performance * improve performance * Update timeline.test.ts
Diffstat (limited to 'packages/frontend/src/components')
-rw-r--r--packages/frontend/src/components/MkButton.vue10
-rw-r--r--packages/frontend/src/components/MkDateSeparatedList.vue22
-rw-r--r--packages/frontend/src/components/MkFukidashi.vue9
-rw-r--r--packages/frontend/src/components/MkMediaList.vue1
-rw-r--r--packages/frontend/src/components/MkMenu.vue66
-rw-r--r--packages/frontend/src/components/MkPagination.vue39
-rw-r--r--packages/frontend/src/components/MkPolkadots.vue40
-rw-r--r--packages/frontend/src/components/MkUrlPreview.vue1
8 files changed, 142 insertions, 46 deletions
diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index 5e89dfba12..891af7f696 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<button
v-if="!link"
ref="el" class="_button"
- :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike, [$style.iconOnly]: iconOnly }]"
+ :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike, [$style.iconOnly]: iconOnly, [$style.wait]: wait }]"
:type="type"
:name="name"
:value="value"
- :disabled="disabled"
+ :disabled="disabled || wait"
@click="emit('click', $event)"
@mousedown="onMousedown"
>
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</button>
<MkA
v-else class="_button"
- :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike, [$style.iconOnly]: iconOnly }]"
+ :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike, [$style.iconOnly]: iconOnly, [$style.wait]: wait }]"
:to="to ?? '#'"
:behavior="linkBehavior"
@mousedown="onMousedown"
@@ -256,6 +256,10 @@ function onMousedown(evt: MouseEvent): void {
opacity: 0.5;
}
+ &.wait {
+ cursor: wait !important;
+ }
+
&:focus-visible {
outline-offset: 2px;
}
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index b5842876ac..ec6fcdc311 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -168,21 +168,17 @@ export default defineComponent({
container-type: inline-size;
&:global {
- > .list-move {
- transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
- }
-
- &.deny-move-transition > .list-move {
- transition: none !important;
- }
+ > .list-move {
+ transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
+ }
- > .list-enter-active {
- transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
- }
+ > .list-enter-active {
+ transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
+ }
- > *:empty {
- display: none;
- }
+ > *:empty {
+ display: none;
+ }
}
&:not(.date-separated-list-nogap) > *:not(:last-child) {
diff --git a/packages/frontend/src/components/MkFukidashi.vue b/packages/frontend/src/components/MkFukidashi.vue
index 8b1c56fca4..e9544afa35 100644
--- a/packages/frontend/src/components/MkFukidashi.vue
+++ b/packages/frontend/src/components/MkFukidashi.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
tail === 'left' ? $style.left : $style.right,
negativeMargin === true && $style.negativeMargin,
shadow === true && $style.shadow,
+ accented === true && $style.accented
]"
>
<div :class="$style.bg">
@@ -30,10 +31,12 @@ withDefaults(defineProps<{
tail?: 'left' | 'right' | 'none';
negativeMargin?: boolean;
shadow?: boolean;
+ accented?: boolean;
}>(), {
tail: 'right',
negativeMargin: false,
shadow: false,
+ accented: false,
});
</script>
@@ -47,6 +50,10 @@ withDefaults(defineProps<{
min-height: calc(var(--fukidashi-radius) * 2);
padding-top: calc(var(--fukidashi-radius) * .13);
+ &.accented {
+ --fukidashi-bg: var(--MI_THEME-accent);
+ }
+
&.shadow {
filter: drop-shadow(0 4px 32px var(--MI_THEME-shadow));
}
@@ -77,7 +84,7 @@ withDefaults(defineProps<{
.content {
position: relative;
- padding: 8px 12px;
+ padding: 10px 14px;
}
.tail {
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index ae15776041..4a1100c324 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -227,7 +227,6 @@ defineExpose({
.container {
position: relative;
width: 100%;
- margin-top: 4px;
}
.medias {
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index a84bd9b256..f2f36308ca 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.center]: align === 'center',
[$style.big]: big,
[$style.asDrawer]: asDrawer,
+ [$style.widthSpecified]: width != null,
}"
@focusin.passive.stop="() => {}"
>
@@ -29,15 +30,19 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<template v-for="item in (items2 ?? [])">
<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
+
<span v-else-if="item.type === 'label'" role="menuitem" tabindex="-1" :class="[$style.label, $style.item]">
<span style="opacity: 0.7;">{{ item.text }}</span>
</span>
+
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
<span><MkEllipsis/></span>
</span>
+
<div v-else-if="item.type === 'component'" role="menuitem" tabindex="-1" :class="[$style.componentItem]">
<component :is="item.component" v-bind="item.props"/>
</div>
+
<MkA
v-else-if="item.type === 'link'"
role="menuitem"
@@ -51,10 +56,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
<div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
+ <div :class="$style.item_content_text">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</MkA>
+
<a
v-else-if="item.type === 'a'"
role="menuitem"
@@ -70,10 +79,14 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
+ <div :class="$style.item_content_text">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</a>
+
<button
v-else-if="item.type === 'user'"
role="menuitem"
@@ -88,6 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</button>
+
<button
v-else-if="item.type === 'switch'"
role="menuitemcheckbox"
@@ -101,10 +115,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<MkSwitchButton v-else :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
<div :class="$style.item_content">
- <span :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">{{ item.text }}</span>
+ <div :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<MkSwitchButton v-if="item.icon" :class="[$style.switchButton, $style.caret]" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
</div>
</button>
+
<button
v-else-if="item.type === 'radio'"
role="menuitem"
@@ -117,10 +135,14 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
<div :class="$style.item_content">
- <span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
+ <div :class="$style.item_content_text" style="pointer-events: none;">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
</div>
</button>
+
<button
v-else-if="item.type === 'radioOption'"
role="menuitemradio"
@@ -134,9 +156,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="[$style.radioIcon, { [$style.radioChecked]: unref(item.active) }]"></span>
</div>
<div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
+ <div :class="$style.item_content_text">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
</div>
</button>
+
<button
v-else-if="item.type === 'parent'"
role="menuitem"
@@ -148,12 +174,17 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
<div :class="$style.item_content">
- <span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
+ <div :class="$style.item_content_text" style="pointer-events: none;">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
</div>
</button>
+
<button
- v-else role="menuitem"
+ v-else
+ role="menuitem"
tabindex="0"
:class="['_button', $style.item, { [$style.danger]: item.danger, [$style.active]: unref(item.active) }]"
@click.prevent="unref(item.active) ? close(false) : clicked(item.action, $event)"
@@ -163,11 +194,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
<div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
+ <div :class="$style.item_content_text">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</button>
</template>
+
<span v-if="items2 == null || items2.length === 0" tabindex="-1" :class="[$style.none, $style.item]">
<span>{{ i18n.ts.none }}</span>
</span>
@@ -438,6 +473,12 @@ onBeforeUnmount(() => {
}
}
+ &:not(.widthSpecified) {
+ > .menu {
+ max-width: 400px;
+ }
+ }
+
&.big:not(.asDrawer) {
> .menu {
min-width: 230px;
@@ -607,10 +648,19 @@ onBeforeUnmount(() => {
.item_content_text {
max-width: calc(100vw - 4rem);
+}
+
+.item_content_text_title {
text-overflow: ellipsis;
overflow: hidden;
}
+.item_content_text_caption {
+ text-wrap: auto;
+ font-size: 85%;
+ opacity: 0.7;
+}
+
.switchButton {
margin-left: -2px;
--height: 1.35em;
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index ab8bda403b..a729619180 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -24,16 +24,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</slot>
</div>
- <div v-else ref="rootEl">
- <div v-show="pagination.reversed && more" key="_more_" class="_margin">
- <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMoreAhead : null" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary rounded @click="fetchMoreAhead">
+ <div v-else ref="rootEl" class="_gaps">
+ <div v-show="pagination.reversed && more" key="_more_">
+ <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMoreAhead : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMoreAhead">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
</div>
<slot :items="Array.from(items.values())" :fetching="fetching || moreFetching"></slot>
- <div v-show="!pagination.reversed && more" key="_more_" class="_margin">
- <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMore : null" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary rounded @click="fetchMore">
+ <div v-show="!pagination.reversed && more" key="_more_">
+ <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMore : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMore">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
-import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
+import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible } from '@@/js/scroll.js';
import type { ComputedRef } from 'vue';
import type { MisskeyEntity } from '@/types/date-separated-list.js';
import { misskeyApi } from '@/utility/misskey-api.js';
@@ -74,8 +74,6 @@ export type Paging<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints>
reversed?: boolean;
offsetMode?: boolean;
-
- pageEl?: HTMLElement;
};
type MisskeyEntityMap = Map<string, MisskeyEntity>;
@@ -141,8 +139,7 @@ const {
enableInfiniteScroll,
} = prefer.r;
-const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value);
-const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : window.document.body);
+const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : window.document.body);
const visibility = useDocumentVisibility();
@@ -173,13 +170,13 @@ watch(rootEl, () => {
});
});
-watch([backed, contentEl], () => {
+watch([backed, rootEl], () => {
if (!backed.value) {
- if (!contentEl.value) return;
+ if (!rootEl.value) return;
scrollRemove.value = props.pagination.reversed
- ? onScrollBottom(contentEl.value, executeQueue, TOLERANCE)
- : onScrollTop(contentEl.value, (topVisible) => { if (topVisible) executeQueue(); }, TOLERANCE);
+ ? onScrollBottom(rootEl.value, executeQueue, TOLERANCE)
+ : onScrollTop(rootEl.value, (topVisible) => { if (topVisible) executeQueue(); }, TOLERANCE);
} else {
if (scrollRemove.value) scrollRemove.value();
scrollRemove.value = null;
@@ -349,7 +346,7 @@ const appearFetchMoreAhead = async (): Promise<void> => {
fetchMoreAppearTimeout();
};
-const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE);
+const isHead = (): boolean => isBackTop.value || (props.pagination.reversed ? isTailVisible : isHeadVisible)(rootEl.value!, TOLERANCE);
watch(visibility, () => {
if (visibility.value === 'hidden') {
@@ -364,7 +361,7 @@ watch(visibility, () => {
timerForSetPause = null;
} else {
isPausingUpdate = false;
- if (isTop()) {
+ if (isHead()) {
executeQueue();
}
}
@@ -376,16 +373,18 @@ watch(visibility, () => {
* ストリーミングから降ってきたアイテムはこれで追加する
* @param item アイテム
*/
-const prepend = (item: MisskeyEntity): void => {
+function prepend(item: MisskeyEntity): void {
if (items.value.size === 0) {
items.value.set(item.id, item);
fetching.value = false;
return;
}
- if (isTop() && !isPausingUpdate) unshiftItems([item]);
+ console.log(isHead(), isPausingUpdate);
+
+ if (isHead() && !isPausingUpdate) unshiftItems([item]);
else prependQueue(item);
-};
+}
/**
* 新着アイテムをitemsの先頭に追加し、displayLimitを適用する
@@ -447,7 +446,7 @@ onDeactivated(() => {
});
function toBottom() {
- scrollToBottom(contentEl.value!);
+ scrollToBottom(rootEl.value!);
}
onBeforeMount(() => {
diff --git a/packages/frontend/src/components/MkPolkadots.vue b/packages/frontend/src/components/MkPolkadots.vue
new file mode 100644
index 0000000000..285c4d0b79
--- /dev/null
+++ b/packages/frontend/src/components/MkPolkadots.vue
@@ -0,0 +1,40 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.root, accented ? $style.accented : null]"></div>
+</template>
+
+<script lang="ts" setup>
+const props = withDefaults(defineProps<{
+ accented?: boolean;
+}>(), {
+ accented: false,
+});
+</script>
+
+<style lang="scss" module>
+.root {
+ --c: var(--MI_THEME-divider);
+
+ &.accented {
+ --c: var(--MI_THEME-accent);
+ opacity: 0.5;
+ }
+
+ --dot-size: 2px;
+ --gap-size: 40px;
+ --offset: calc(var(--gap-size) / 2);
+
+ height: 200px;
+ margin-bottom: -200px;
+
+ background-image: linear-gradient(transparent 60%, transparent 100%), radial-gradient(var(--c) var(--dot-size), transparent var(--dot-size)), radial-gradient(var(--c) var(--dot-size), transparent var(--dot-size));
+ background-position: 0 0, 0 0, var(--offset) var(--offset);
+ background-size: 100% 100%, var(--gap-size) var(--gap-size), var(--gap-size) var(--gap-size);
+ mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
+ pointer-events: none;
+}
+</style>
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index f20aee0ce3..20dab6f028 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -246,6 +246,7 @@ onUnmounted(() => {
box-shadow: 0 0 0 1px var(--MI_THEME-divider);
border-radius: 8px;
overflow: clip;
+ text-align: left;
&:hover {
text-decoration: none;