summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-02-26 20:21:54 +0900
committerGitHub <noreply@github.com>2023-02-26 20:21:54 +0900
commit02c8fd9de51b6a8471ab9a89f23bcfeaecd7626c (patch)
tree018a46cad9a19cc8cdfcff91442b343a637b56f8 /packages/frontend/src/components
parentMerge pull request #10058 from misskey-dev/develop (diff)
parentMerge branch 'develop' of https://github.com/misskey-dev/misskey into develop (diff)
downloadmisskey-02c8fd9de51b6a8471ab9a89f23bcfeaecd7626c.tar.gz
misskey-02c8fd9de51b6a8471ab9a89f23bcfeaecd7626c.tar.bz2
misskey-02c8fd9de51b6a8471ab9a89f23bcfeaecd7626c.zip
Merge pull request #10108 from misskey-dev/develop
* Add dialog to remove follower (#9718) * update PULL_REQUEST_TEMPLATE * 起動時にRedisの疎通確認を行う (#9832) * 起動時にRedisの疎通確認を行う * check:connectをstart内に移動 --------- Co-authored-by: tamaina <tamaina@hotmail.co.jp> * Pass `--detectOpenHandles` to Jest (#9895) Co-authored-by: tamaina <tamaina@hotmail.co.jp> * enhance(client): MkUrlPreviewの閉じるボタンを見やすく (#9913) Co-authored-by: tamaina <tamaina@hotmail.co.jp> * test(backend): restore ap-request tests (#9997) Co-authored-by: tamaina <tamaina@hotmail.co.jp> * fix/refaftor(client): MkTime.vueの変更 (#10061) * fix(client): MkTime.timeにstringでもDateでない値が入った場合、?を表示 * fix(client): MkTimeを改良 * numberを許容 * falsyな値もとる * 不明 * ありません * fix * fix(server): notes/createで、fileIdsと見つかったファイルの数が異なる場合はエラーにする (#9911) * fix(server): notes/createで、fileIdsと見つかったファイルの数が異なる場合はエラーにする * NO_SUCH_FILE * Update codecov.yml * Update apple-touch-icon.png * デプロイされているプレビュー環境がない場合はプレビュー環境を削除しないようにする (#10062) * デプロイされているプレビュー環境がない場合はDestroy preview environmentを実行しないようにする * CIがない場合の処理追加 * enhance(client): improve clip menu ux * 未知のユーザーが deleteActor されたら処理をスキップする (#10067) * fix(client): Android ChromeでPWAとしてインストールできない問題を修正 (#10069) * fix(client): Android ChromeでPWAとしてインストールできない問題を修正 * 順番関係ある? * Windows環境でswcを使うと正常にビルドができない問題の修正 (#10074) * Update @swc/core to v1.3.36 * Update CHANGELOG.md * Update CHANGELOG.md * バックグラウンドで一定時間経過したらページネーションのアイテム更新をしない (#10053) * :art: * feat: 2つの検索画面の統合 (#9949) (#10038) * feat: 検索画面の UI を統一 * fix: エラーの修正 * add: changelog --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * enhance(client): ノートメニューからユーザーメニューを開けるように Resolve #10019 * enhance(client): renoteした際の表示を改善 Resolve #10078 * Update CHANGELOG.md * enhance(client): tweak contextmenu position calculation * :art: * :art: * feat: in-channel featured note Resolve #9938 * refactor(frontend): fix eslint error (#10084) * Simplify search.vue (remove dead code) (#10088) * Simplify search.vue This is already handled by the code above it, no need to handle it twice * Remove unused imports * Update about-misskey.vue * test(server): add validation test of api:notes/create (#10090) * fix(server): notes/createのバリデーションが効いていない Fix #10079 Co-Authored-By: mei23 <m@m544.net> * anyOf内にバリデーションを書いても最初の一つしかチェックされない * :v: * wip * wip * :v: * RequiredProp * Revert "RequiredProp" This reverts commit 74693900119a590263106fa3adefd008d69ce80c. * add api:notes/create * fix lint * text * :v: * improve readability --------- Co-authored-by: mei23 <m@m544.net> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * New Crowdin updates (#10059) * New translations ja-JP.yml (Japanese, Kansai) * New translations ja-JP.yml (Romanian) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Spanish) * New translations ja-JP.yml (Arabic) * New translations ja-JP.yml (Czech) * New translations ja-JP.yml (German) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Korean) * New translations ja-JP.yml (Polish) * New translations ja-JP.yml (Russian) * New translations ja-JP.yml (Slovak) * New translations ja-JP.yml (Ukrainian) * New translations ja-JP.yml (Chinese Simplified) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Vietnamese) * New translations ja-JP.yml (Indonesian) * New translations ja-JP.yml (Bengali) * New translations ja-JP.yml (Thai) * New translations ja-JP.yml (Japanese, Kansai) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (German) * New translations ja-JP.yml (English) * New translations ja-JP.yml (German) * New translations ja-JP.yml (Ukrainian) * New translations ja-JP.yml (Chinese Simplified) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Ukrainian) * New translations ja-JP.yml (Ukrainian) * New translations ja-JP.yml (Ukrainian) * New translations ja-JP.yml (Chinese Simplified) * New translations ja-JP.yml (Japanese, Kansai) * New translations ja-JP.yml (Thai) * New translations ja-JP.yml (Thai) * New translations ja-JP.yml (Thai) * New translations ja-JP.yml (Spanish) * New translations ja-JP.yml (Spanish) * enhance(client): improve user menu ux * enhance(client): photoswipe 表示時に戻る操作をしても前の画面に戻らないように (#10098) * enhance(client): photoswipe 表示時に戻る操作をしても前の画面に戻らないように * add: changelog --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * enhance(client): メニューの「もっと」からインスタンス情報を見れるように * [Fix] fixed an typo in error message (#10102) * Update codecov.yml * Update CHANGELOG.md * fix(server): エラーのスタックトレースは返さないように Fix #10064 * [chore]Editorconfig: ymlに加えてyamlファイルに対しても同じ規約を適用する (#10081) * Added yaml file in addition to yml file, in editorconfig * Applied editorconfig for pnpm-workspace.yaml --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * update deps * ホームタイムラインの読み込みでクエリタイムアウトになるのを修正する (#10106) * refactor * New translations ja-JP.yml (French) (#10103) * Update CHANGELOG.md * 13.8.0 --------- Co-authored-by: atsuchan <83960488+atsu1125@users.noreply.github.com> Co-authored-by: Masaya Suzuki <15100604+massongit@users.noreply.github.com> Co-authored-by: tamaina <tamaina@hotmail.co.jp> Co-authored-by: Kagami Sascha Rosylight <saschanaz@outlook.com> Co-authored-by: taiy <53635909+taiyme@users.noreply.github.com> Co-authored-by: xianon <xianon@hotmail.co.jp> Co-authored-by: kabo2468 <28654659+kabo2468@users.noreply.github.com> Co-authored-by: YS <47836716+yszkst@users.noreply.github.com> Co-authored-by: Khsmty <me@khsmty.com> Co-authored-by: Soni L <EnderMoneyMod@gmail.com> Co-authored-by: mei23 <m@m544.net> Co-authored-by: daima3629 <52790780+daima3629@users.noreply.github.com> Co-authored-by: Windymelt <1113940+windymelt@users.noreply.github.com>
Diffstat (limited to 'packages/frontend/src/components')
-rw-r--r--packages/frontend/src/components/MkContextMenu.vue10
-rw-r--r--packages/frontend/src/components/MkMediaList.vue17
-rw-r--r--packages/frontend/src/components/MkMenu.child.vue35
-rw-r--r--packages/frontend/src/components/MkMenu.vue28
-rw-r--r--packages/frontend/src/components/MkModal.vue23
-rw-r--r--packages/frontend/src/components/MkNote.vue46
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue4
-rw-r--r--packages/frontend/src/components/MkPagination.vue37
-rw-r--r--packages/frontend/src/components/MkUrlPreview.vue29
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.tabs.vue2
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.vue5
-rw-r--r--packages/frontend/src/components/global/MkTime.vue25
12 files changed, 199 insertions, 62 deletions
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index f0ea984c4e..21cccaabde 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -32,6 +32,8 @@ let rootEl = $shallowRef<HTMLDivElement>();
let zIndex = $ref<number>(os.claimZIndex('high'));
+const SCROLLBAR_THICKNESS = 16;
+
onMounted(() => {
let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
@@ -39,12 +41,12 @@ onMounted(() => {
const width = rootEl.offsetWidth;
const height = rootEl.offsetHeight;
- if (left + width - window.pageXOffset > window.innerWidth) {
- left = window.innerWidth - width + window.pageXOffset;
+ if (left + width - window.pageXOffset >= (window.innerWidth - SCROLLBAR_THICKNESS)) {
+ left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.pageXOffset;
}
- if (top + height - window.pageYOffset > window.innerHeight) {
- top = window.innerHeight - height + window.pageYOffset;
+ if (top + height - window.pageYOffset >= (window.innerHeight - SCROLLBAR_THICKNESS)) {
+ top = (window.innerHeight - SCROLLBAR_THICKNESS) - height + window.pageYOffset;
}
if (top < 0) {
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index a12bb78e35..fafa0bd232 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -113,6 +113,23 @@ onMounted(() => {
});
lightbox.init();
+
+ window.addEventListener('popstate', () => {
+ if (lightbox.pswp && lightbox.pswp.isOpen === true) {
+ lightbox.pswp.close();
+ return;
+ }
+ });
+
+ lightbox.on('beforeOpen', () => {
+ history.pushState(null, '', '#pswp');
+ });
+
+ lightbox.on('close', () => {
+ if (window.location.hash === '#pswp') {
+ history.back();
+ }
+ });
});
const previewable = (file: misskey.entities.DriveFile): boolean => {
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index cdd9d96b96..e0935efbe7 100644
--- a/packages/frontend/src/components/MkMenu.child.vue
+++ b/packages/frontend/src/components/MkMenu.child.vue
@@ -1,11 +1,11 @@
<template>
-<div ref="el" class="sfhdhdhr">
- <MkMenu ref="menu" :items="items" :align="align" :width="width" :as-drawer="false" @close="onChildClosed"/>
+<div ref="el" :class="$style.root">
+ <MkMenu :items="items" :align="align" :width="width" :as-drawer="false" @close="onChildClosed"/>
</div>
</template>
<script lang="ts" setup>
-import { nextTick, onMounted, shallowRef, watch } from 'vue';
+import { nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
import MkMenu from './MkMenu.vue';
import { MenuItem } from '@/types/menu';
@@ -25,11 +25,21 @@ const emit = defineEmits<{
const el = shallowRef<HTMLElement>();
const align = 'left';
+const SCROLLBAR_THICKNESS = 16;
+
function setPosition() {
const rootRect = props.rootElement.getBoundingClientRect();
- const rect = props.targetElement.getBoundingClientRect();
- const left = props.targetElement.offsetWidth;
- const top = (rect.top - rootRect.top) - 8;
+ const parentRect = props.targetElement.getBoundingClientRect();
+ const myRect = el.value.getBoundingClientRect();
+
+ let left = props.targetElement.offsetWidth;
+ let top = (parentRect.top - rootRect.top) - 8;
+ if (rootRect.left + left + myRect.width >= (window.innerWidth - SCROLLBAR_THICKNESS)) {
+ left = -myRect.width;
+ }
+ if (rootRect.top + top + myRect.height >= (window.innerHeight - SCROLLBAR_THICKNESS)) {
+ top = top - ((rootRect.top + top + myRect.height) - (window.innerHeight - SCROLLBAR_THICKNESS));
+ }
el.value.style.left = left + 'px';
el.value.style.top = top + 'px';
}
@@ -46,13 +56,22 @@ watch(() => props.targetElement, () => {
setPosition();
});
+const ro = new ResizeObserver((entries, observer) => {
+ setPosition();
+});
+
onMounted(() => {
+ ro.observe(el.value);
setPosition();
nextTick(() => {
setPosition();
});
});
+onUnmounted(() => {
+ ro.disconnect();
+});
+
defineExpose({
checkHit: (ev: MouseEvent) => {
return (ev.target === el.value || el.value.contains(ev.target));
@@ -60,8 +79,8 @@ defineExpose({
});
</script>
-<style lang="scss" scoped>
-.sfhdhdhr {
+<style lang="scss" module>
+.root {
position: absolute;
}
</style>
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index 52aba58455..09d530c4ea 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -56,7 +56,7 @@
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, watch } from 'vue';
+import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { focusPrev, focusNext } from '@/scripts/focus';
import MkSwitch from '@/components/MkSwitch.vue';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu';
@@ -111,11 +111,11 @@ watch(() => props.items, () => {
immediate: true,
});
-let childMenu = $ref<MenuItem[] | null>();
+let childMenu = ref<MenuItem[] | null>();
let childTarget = $shallowRef<HTMLElement | null>();
function closeChild() {
- childMenu = null;
+ childMenu.value = null;
childShowingItem = null;
}
@@ -140,13 +140,31 @@ function onItemMouseLeave(item) {
if (childCloseTimer) window.clearTimeout(childCloseTimer);
}
+let childrenCache = new WeakMap();
async function showChildren(item: MenuItem, ev: MouseEvent) {
+ const children = ref([]);
+ if (childrenCache.has(item)) {
+ children.value = childrenCache.get(item);
+ } else {
+ if (typeof item.children === 'function') {
+ children.value = [{
+ type: 'pending',
+ }];
+ item.children().then(x => {
+ children.value = x;
+ childrenCache.set(item, x);
+ });
+ } else {
+ children.value = item.children;
+ }
+ }
+
if (props.asDrawer) {
- os.popupMenu(item.children, ev.currentTarget ?? ev.target);
+ os.popupMenu(children, ev.currentTarget ?? ev.target);
close();
} else {
childTarget = ev.currentTarget ?? ev.target;
- childMenu = item.children;
+ childMenu = children;
childShowingItem = item;
}
}
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index eba0f5847d..dbad02fb7e 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -125,7 +125,7 @@ function onBgClick() {
}
if (type === 'drawer') {
- maxHeight = window.innerHeight / 1.5;
+ maxHeight = (window.innerHeight - SCROLLBAR_THICKNESS) / 1.5;
}
const keymap = {
@@ -133,6 +133,7 @@ const keymap = {
};
const MARGIN = 16;
+const SCROLLBAR_THICKNESS = 16;
const align = () => {
if (props.src == null) return;
@@ -170,15 +171,15 @@ const align = () => {
if (fixed) {
// 画面から横にはみ出る場合
- if (left + width > window.innerWidth) {
- left = window.innerWidth - width;
+ if (left + width > (window.innerWidth - SCROLLBAR_THICKNESS)) {
+ left = (window.innerWidth - SCROLLBAR_THICKNESS) - width;
}
- const underSpace = (window.innerHeight - MARGIN) - top;
+ const underSpace = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - top;
const upperSpace = (srcRect.top - MARGIN);
// 画面から縦にはみ出る場合
- if (top + height > (window.innerHeight - MARGIN)) {
+ if (top + height > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
if (props.noOverlap && props.anchor.x === 'center') {
if (underSpace >= (upperSpace / 3)) {
maxHeight = underSpace;
@@ -187,22 +188,22 @@ const align = () => {
top = (upperSpace + MARGIN) - height;
}
} else {
- top = (window.innerHeight - MARGIN) - height;
+ top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height;
}
} else {
maxHeight = underSpace;
}
} else {
// 画面から横にはみ出る場合
- if (left + width - window.pageXOffset > window.innerWidth) {
- left = window.innerWidth - width + window.pageXOffset - 1;
+ if (left + width - window.pageXOffset > (window.innerWidth - SCROLLBAR_THICKNESS)) {
+ left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.pageXOffset - 1;
}
- const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset);
+ const underSpace = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - (top - window.pageYOffset);
const upperSpace = (srcRect.top - MARGIN);
// 画面から縦にはみ出る場合
- if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) {
+ if (top + height - window.pageYOffset > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
if (props.noOverlap && props.anchor.x === 'center') {
if (underSpace >= (upperSpace / 3)) {
maxHeight = underSpace;
@@ -211,7 +212,7 @@ const align = () => {
top = window.pageYOffset + ((upperSpace + MARGIN) - height);
}
} else {
- top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1;
+ top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height + window.pageYOffset - 1;
}
} else {
maxHeight = underSpace;
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index ffc3133a85..1040dac12e 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -255,7 +255,7 @@ function renote(viaKeyboard = false) {
text: i18n.ts.inChannelRenote,
icon: 'ti ti-repeat',
action: () => {
- os.api('notes/create', {
+ os.apiWithDialog('notes/create', {
renoteId: appearNote.id,
channelId: appearNote.channelId,
});
@@ -276,7 +276,7 @@ function renote(viaKeyboard = false) {
text: i18n.ts.renote,
icon: 'ti ti-repeat',
action: () => {
- os.api('notes/create', {
+ os.apiWithDialog('notes/create', {
renoteId: appearNote.id,
});
},
@@ -673,9 +673,17 @@ function showReactions(): void {
opacity: 0.7;
}
-@container (max-width: 500px) {
+@container (max-width: 580px) {
.root {
- font-size: 0.9em;
+ font-size: 0.95em;
+ }
+
+ .renote {
+ padding: 12px 26px 0 26px;
+ }
+
+ .article {
+ padding: 24px 26px 14px;
}
.avatar {
@@ -684,7 +692,21 @@ function showReactions(): void {
}
}
-@container (max-width: 450px) {
+@container (max-width: 500px) {
+ .root {
+ font-size: 0.9em;
+ }
+
+ .renote {
+ padding: 10px 22px 0 22px;
+ }
+
+ .article {
+ padding: 20px 22px 12px;
+ }
+}
+
+@container (max-width: 480px) {
.renote {
padding: 8px 16px 0 16px;
}
@@ -701,7 +723,9 @@ function showReactions(): void {
.article {
padding: 14px 16px 9px;
}
+}
+@container (max-width: 450px) {
.avatar {
margin: 0 10px 8px 0;
width: 46px;
@@ -710,7 +734,7 @@ function showReactions(): void {
}
}
-@container (max-width: 350px) {
+@container (max-width: 400px) {
.footerButton {
&:not(:last-child) {
margin-right: 18px;
@@ -718,6 +742,14 @@ function showReactions(): void {
}
}
+@container (max-width: 350px) {
+ .footerButton {
+ &:not(:last-child) {
+ margin-right: 12px;
+ }
+ }
+}
+
@container (max-width: 300px) {
.avatar {
width: 44px;
@@ -726,7 +758,7 @@ function showReactions(): void {
.footerButton {
&:not(:last-child) {
- margin-right: 12px;
+ margin-right: 8px;
}
}
}
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 3ec1ce95f4..2eebe999a5 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -250,7 +250,7 @@ function renote(viaKeyboard = false) {
text: i18n.ts.inChannelRenote,
icon: 'ti ti-repeat',
action: () => {
- os.api('notes/create', {
+ os.apiWithDialog('notes/create', {
renoteId: appearNote.id,
channelId: appearNote.channelId,
});
@@ -271,7 +271,7 @@ function renote(viaKeyboard = false) {
text: i18n.ts.renote,
icon: 'ti ti-repeat',
action: () => {
- os.api('notes/create', {
+ os.apiWithDialog('notes/create', {
renoteId: appearNote.id,
});
},
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index 84ba94361e..378d0ac020 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -42,6 +42,7 @@ import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeUnmount, o
import * as misskey from 'misskey-js';
import * as os from '@/os';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll';
+import { useDocumentVisibility } from '@/scripts/use-document-visibility';
import MkButton from '@/components/MkButton.vue';
import { defaultStore } from '@/store';
import { MisskeyEntity } from '@/types/date-separated-list';
@@ -107,6 +108,12 @@ const {
const contentEl = $computed(() => props.pagination.pageEl ?? rootEl);
const scrollableElement = $computed(() => getScrollContainer(contentEl));
+const visibility = useDocumentVisibility();
+
+let isPausingUpdate = false;
+let timerForSetPause: number | null = null;
+const BACKGROUND_PAUSE_WAIT_SEC = 10;
+
// 先頭が表示されているかどうかを検出
// https://qiita.com/mkataigi/items/0154aefd2223ce23398e
let scrollObserver = $ref<IntersectionObserver>();
@@ -279,6 +286,28 @@ const fetchMoreAhead = async (): Promise<void> => {
});
};
+const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl, TOLERANCE);
+
+watch(visibility, () => {
+ if (visibility.value === 'hidden') {
+ timerForSetPause = window.setTimeout(() => {
+ isPausingUpdate = true;
+ timerForSetPause = null;
+ },
+ BACKGROUND_PAUSE_WAIT_SEC * 1000);
+ } else { // 'visible'
+ if (timerForSetPause) {
+ clearTimeout(timerForSetPause);
+ timerForSetPause = null;
+ } else {
+ isPausingUpdate = false;
+ if (isTop()) {
+ executeQueue();
+ }
+ }
+ }
+});
+
const prepend = (item: MisskeyEntity): void => {
// 初回表示時はunshiftだけでOK
if (!rootEl) {
@@ -286,9 +315,7 @@ const prepend = (item: MisskeyEntity): void => {
return;
}
- const isTop = isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl, TOLERANCE);
-
- if (isTop) unshiftItems([item]);
+ if (isTop() && !isPausingUpdate) unshiftItems([item]);
else prependQueue(item);
};
@@ -357,6 +384,10 @@ onMounted(() => {
});
onBeforeUnmount(() => {
+ if (timerForSetPause) {
+ clearTimeout(timerForSetPause);
+ timerForSetPause = null;
+ }
scrollObserver.disconnect();
});
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index b97b7cf07b..5381ecbfa5 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -1,12 +1,25 @@
<template>
-<div v-if="playerEnabled" :class="$style.player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`">
- <button :class="$style.disablePlayer" :title="i18n.ts.disablePlayer" @click="playerEnabled = false"><i class="ti ti-x"></i></button>
- <iframe v-if="player.url.startsWith('http://') || player.url.startsWith('https://')" :class="$style.playerIframe" :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/>
- <span v-else>invalid url</span>
-</div>
-<div v-else-if="tweetId && tweetExpanded" ref="twitter" :class="$style.twitter">
- <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${$store.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"></iframe>
-</div>
+<template v-if="playerEnabled">
+ <div :class="$style.player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`">
+ <iframe v-if="player.url.startsWith('http://') || player.url.startsWith('https://')" :class="$style.playerIframe" :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/>
+ <span v-else>invalid url</span>
+ </div>
+ <div :class="$style.action">
+ <MkButton :small="true" inline @click="playerEnabled = false">
+ <i class="ti ti-x"></i> {{ i18n.ts.disablePlayer }}
+ </MkButton>
+ </div>
+</template>
+<template v-else-if="tweetId && tweetExpanded">
+ <div ref="twitter" :class="$style.twitter">
+ <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${$store.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"></iframe>
+ </div>
+ <div :class="$style.action">
+ <MkButton :small="true" inline @click="tweetExpanded = false">
+ <i class="ti ti-x"></i> {{ i18n.ts.close }}
+ </MkButton>
+ </div>
+</template>
<div v-else :class="$style.urlPreview">
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" :class="$style.thumbnail" :style="`background-image: url('${thumbnail}')`">
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
index 3beedf34b2..42760da08f 100644
--- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
@@ -103,7 +103,7 @@ function onTabWheel(ev: WheelEvent) {
ev.stopPropagation();
(ev.currentTarget as HTMLElement).scrollBy({
left: ev.deltaY,
- behavior: 'smooth',
+ behavior: 'instant',
});
}
return false;
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index a4e25bbe1a..589ca92d75 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -147,10 +147,7 @@ onUnmounted(() => {
.tabs:first-child {
margin-left: auto;
- }
- .tabs:not(:first-child) {
- padding-left: 16px;
- mask-image: linear-gradient(90deg, rgba(0,0,0,0), rgb(0,0,0) 16px, rgb(0,0,0) 100%);
+ padding: 0 12px;
}
.tabs {
margin-right: auto;
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 66c0bd5135..3fa8bb9adc 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -1,6 +1,7 @@
<template>
<time :title="absolute">
- <template v-if="mode === 'relative'">{{ relative }}</template>
+ <template v-if="invalid">{{ i18n.ts._ago.invalid }}</template>
+ <template v-else-if="mode === 'relative'">{{ relative }}</template>
<template v-else-if="mode === 'absolute'">{{ absolute }}</template>
<template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template>
</time>
@@ -12,18 +13,24 @@ import { i18n } from '@/i18n';
import { dateTimeFormat } from '@/scripts/intl-const';
const props = withDefaults(defineProps<{
- time: Date | string;
+ time: Date | string | number | null;
mode?: 'relative' | 'absolute' | 'detail';
}>(), {
mode: 'relative',
});
-const _time = typeof props.time === 'string' ? new Date(props.time) : props.time;
-const absolute = dateTimeFormat.format(_time);
+const _time = props.time == null ? NaN :
+ typeof props.time === 'number' ? props.time :
+ (props.time instanceof Date ? props.time : new Date(props.time)).getTime();
+const invalid = Number.isNaN(_time);
+const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
-let now = $shallowRef(new Date());
-const relative = $computed(() => {
- const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/;
+let now = $ref((new Date()).getTime());
+const relative = $computed<string>(() => {
+ if (props.mode === 'absolute') return ''; // absoluteではrelativeを使わないので計算しない
+ if (invalid) return i18n.ts._ago.invalid;
+
+ const ago = (now - _time) / 1000/*ms*/;
return (
ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() }) :
ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() }) :
@@ -39,8 +46,8 @@ const relative = $computed(() => {
let tickId: number;
function tick() {
- now = new Date();
- const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/;
+ now = (new Date()).getTime();
+ const ago = (now - _time) / 1000/*ms*/;
const next = ago < 60 ? 10000 : ago < 3600 ? 60000 : 180000;
tickId = window.setTimeout(tick, next);