summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmelia Yukii <admin@transfem.org>2024-01-03 19:46:03 +0100
committerAmelia Yukii <admin@transfem.org>2024-01-03 19:46:03 +0100
commitbf5e62301adc677f811975da7b8aa68e993cd486 (patch)
tree4fabc3cdc6ea2af9fcd6d61544321db10d65243e
parentchore: set node version to LTS on docker (diff)
parentRemove leftover console.log (diff)
downloadsharkey-bf5e62301adc677f811975da7b8aa68e993cd486.tar.gz
sharkey-bf5e62301adc677f811975da7b8aa68e993cd486.tar.bz2
sharkey-bf5e62301adc677f811975da7b8aa68e993cd486.zip
feat: Note Highlights
Reviewed-on: https://git.joinsharkey.org/Sharkey/Sharkey/pulls/304
-rw-r--r--packages/frontend/src/components/SkNote.vue14
-rw-r--r--packages/frontend/src/components/SkNoteDetailed.vue58
-rw-r--r--packages/frontend/src/components/SkNoteSub.vue22
-rw-r--r--packages/frontend/src/nirax.ts4
-rw-r--r--packages/frontend/src/router.ts36
5 files changed, 125 insertions, 9 deletions
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index b98e20bad6..b5b23386e7 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -776,6 +776,10 @@ function focusAfter() {
focusNext(el.value);
}
+function scrollIntoView() {
+ el.value.scrollIntoView();
+}
+
function readPromo() {
os.api('promo/read', {
noteId: appearNote.value.id,
@@ -790,6 +794,12 @@ function emitUpdReaction(emoji: string, delta: number) {
emit('reaction', emoji);
}
}
+
+defineExpose({
+ focus,
+ blur,
+ scrollIntoView,
+});
</script>
<style lang="scss" module>
@@ -824,7 +834,7 @@ function emitUpdReaction(emoji: string, delta: number) {
margin: auto;
width: calc(100% - 8px);
height: calc(100% - 8px);
- border: dashed 1px var(--focus);
+ border: solid 1px var(--focus);
border-radius: var(--radius);
box-sizing: border-box;
}
@@ -894,7 +904,7 @@ function emitUpdReaction(emoji: string, delta: number) {
position: relative;
display: flex;
align-items: center;
- padding: 24px 32px 16px calc(32px + var(--avatar) + 14px);
+ padding: 24px 32px 0 calc(32px + var(--avatar) + 14px);
line-height: 28px;
white-space: pre;
color: var(--renote);
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index ca3e8ca3f7..4a06e8f56a 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<SkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note" :expandAllCws="props.expandAllCws"/>
</template>
<SkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo" :expandAllCws="props.expandAllCws"/>
- <article :class="$style.note" @contextmenu.stop="onContextmenu">
+ <article :id="appearNote.id" ref="noteEl" :class="$style.note" tabindex="-1" @contextmenu.stop="onContextmenu">
<header :class="$style.noteHeader">
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
<div style="display: flex; align-items: center; white-space: nowrap; overflow: hidden;">
@@ -228,7 +228,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue';
+import { computed, inject, onMounted, onUnmounted, onUpdated, provide, ref, shallowRef, watch } from 'vue';
import * as mfm from '@sharkey/sfm-js';
import * as Misskey from 'misskey-js';
import SkNoteSub from '@/components/SkNoteSub.vue';
@@ -301,6 +301,7 @@ const isRenote = (
);
const el = shallowRef<HTMLElement>();
+const noteEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const menuVersionsButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
@@ -731,11 +732,11 @@ function showRenoteMenu(viaKeyboard = false): void {
}
function focus() {
- el.value.focus();
+ noteEl.value?.focus();
}
function blur() {
- el.value.blur();
+ noteEl.value?.blur();
}
const repliesLoaded = ref(false);
@@ -776,6 +777,7 @@ function loadConversation() {
noteId: appearNote.value.replyId,
}).then(res => {
conversation.value = res.reverse();
+ focus();
});
}
@@ -792,6 +794,31 @@ function animatedMFM() {
}).then((res) => { if (!res.canceled) allowAnim.value = true; });
}
}
+
+let isScrolling = false;
+
+function setScrolling() {
+ isScrolling = true;
+}
+
+onMounted(() => {
+ document.addEventListener('wheel', setScrolling);
+ isScrolling = false;
+ noteEl.value?.scrollIntoView({ block: 'center' });
+});
+
+onUpdated(() => {
+ if (!isScrolling) {
+ noteEl.value?.scrollIntoView({ block: 'center' });
+ if (location.hash) {
+ location.replace(location.hash); // Jump to highlighted reply
+ }
+ }
+});
+
+onUnmounted(() => {
+ document.removeEventListener('wheel', setScrolling);
+});
</script>
<style lang="scss" module>
@@ -863,6 +890,7 @@ function animatedMFM() {
}
.note {
+ position: relative;
padding: 32px;
font-size: 1.2em;
overflow: hidden;
@@ -870,6 +898,28 @@ function animatedMFM() {
&:hover > .main > .footer > .button {
opacity: 1;
}
+
+ &:focus-visible {
+ outline: none;
+
+ &:after {
+ content: "";
+ pointer-events: none;
+ display: block;
+ position: absolute;
+ z-index: 10;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ margin: auto;
+ width: calc(100% - 8px);
+ height: calc(100% - 8px);
+ border: solid 1px var(--focus);
+ border-radius: var(--radius);
+ box-sizing: border-box;
+ }
+ }
}
.noteHeader {
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index 79e171f34c..8b3ced3761 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -461,7 +461,27 @@ if (props.detail) {
}
.main {
- display: flex;
+ position: relative;
+ display: flex;
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: -12px;
+ right: -12px;
+ left: -12px;
+ bottom: -12px;
+ background: var(--panelHighlight);
+ border-radius: var(--radius);
+ opacity: 0;
+ transition: opacity .2s, background .2s;
+ z-index: -1;
+ }
+
+ &:hover::after,
+ &:focus-within::after {
+ opacity: 1;
+ }
}
.colorBar {
diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts
index 9755bdcb18..9564a754f0 100644
--- a/packages/frontend/src/nirax.ts
+++ b/packages/frontend/src/nirax.ts
@@ -78,7 +78,7 @@ export class Router extends EventEmitter<{
public current: Resolved;
public currentRef: ShallowRef<Resolved> = shallowRef();
public currentRoute: ShallowRef<RouteDef> = shallowRef();
- private currentPath: string;
+ private currentPath = '';
private isLoggedIn: boolean;
private notFoundPageComponent: Component;
private currentKey = Date.now().toString();
@@ -89,7 +89,7 @@ export class Router extends EventEmitter<{
super();
this.routes = routes;
- this.currentPath = currentPath;
+ //this.currentPath = currentPath;
this.isLoggedIn = isLoggedIn;
this.notFoundPageComponent = notFoundPageComponent;
this.navigate(currentPath, null, false);
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index b861afa9a3..f12f7bde79 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -545,12 +545,48 @@ export const mainRouter = new Router(routes, location.pathname + location.search
window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);
+const scrollPosStore = new Map<string, number>();
+let restoring = false;
+
+window.setInterval(() => {
+ if (!restoring) {
+ scrollPosStore.set(window.history.state?.key, window.scrollY);
+ }
+}, 1000);
+
mainRouter.addListener('push', ctx => {
window.history.pushState({ key: ctx.key }, '', ctx.path);
+
+ restoring = true;
+ const scrollPos = scrollPosStore.get(ctx.key) ?? 0;
+ window.scroll({ top: scrollPos, behavior: 'instant' });
+
+ if (scrollPos !== 0) {
+ window.setTimeout(() => {
+ // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
+ window.scroll({ top: scrollPos, behavior: 'instant' });
+ }, 100);
+ restoring = false;
+ } else {
+ restoring = false;
+ }
+});
+
+mainRouter.addListener('same', () => {
+ window.scroll({ top: 0, behavior: 'smooth' });
});
window.addEventListener('popstate', (event) => {
mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
+
+ restoring = true;
+ const scrollPos = scrollPosStore.get(event.state?.key) ?? 0;
+ window.scroll({ top: scrollPos, behavior: 'instant' });
+ window.setTimeout(() => {
+ // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
+ window.scroll({ top: scrollPos, behavior: 'instant' });
+ restoring = false;
+ }, 100);
});
export function useRouter(): Router {