feat: Note Highlights

Reviewed-on: https://git.joinsharkey.org/Sharkey/Sharkey/pulls/304
This commit is contained in:
Amelia Yukii 2024-01-03 19:46:03 +01:00
commit bf5e62301a
5 changed files with 125 additions and 9 deletions

View file

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

View file

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

View file

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

View file

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

View file

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