feat: Note Highlights
Reviewed-on: https://git.joinsharkey.org/Sharkey/Sharkey/pulls/304
This commit is contained in:
		
						commit
						bf5e62301a
					
				
					 5 changed files with 125 additions and 9 deletions
				
			
		| 
						 | 
					@ -776,6 +776,10 @@ function focusAfter() {
 | 
				
			||||||
	focusNext(el.value);
 | 
						focusNext(el.value);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function scrollIntoView() {
 | 
				
			||||||
 | 
						el.value.scrollIntoView();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function readPromo() {
 | 
					function readPromo() {
 | 
				
			||||||
	os.api('promo/read', {
 | 
						os.api('promo/read', {
 | 
				
			||||||
		noteId: appearNote.value.id,
 | 
							noteId: appearNote.value.id,
 | 
				
			||||||
| 
						 | 
					@ -790,6 +794,12 @@ function emitUpdReaction(emoji: string, delta: number) {
 | 
				
			||||||
		emit('reaction', emoji);
 | 
							emit('reaction', emoji);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({
 | 
				
			||||||
 | 
						focus,
 | 
				
			||||||
 | 
						blur,
 | 
				
			||||||
 | 
						scrollIntoView,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" module>
 | 
					<style lang="scss" module>
 | 
				
			||||||
| 
						 | 
					@ -824,7 +834,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 | 
				
			||||||
			margin: auto;
 | 
								margin: auto;
 | 
				
			||||||
			width: calc(100% - 8px);
 | 
								width: calc(100% - 8px);
 | 
				
			||||||
			height: calc(100% - 8px);
 | 
								height: calc(100% - 8px);
 | 
				
			||||||
			border: dashed 1px var(--focus);
 | 
								border: solid 1px var(--focus);
 | 
				
			||||||
			border-radius: var(--radius);
 | 
								border-radius: var(--radius);
 | 
				
			||||||
			box-sizing: border-box;
 | 
								box-sizing: border-box;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -894,7 +904,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
	display: flex;
 | 
						display: flex;
 | 
				
			||||||
	align-items: center;
 | 
						align-items: center;
 | 
				
			||||||
	padding: 24px 32px 16px calc(32px + var(--avatar) + 14px);
 | 
						padding: 24px 32px 0 calc(32px + var(--avatar) + 14px);
 | 
				
			||||||
	line-height: 28px;
 | 
						line-height: 28px;
 | 
				
			||||||
	white-space: pre;
 | 
						white-space: pre;
 | 
				
			||||||
	color: var(--renote);
 | 
						color: var(--renote);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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"/>
 | 
							<SkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note" :expandAllCws="props.expandAllCws"/>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
	<SkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo" :expandAllCws="props.expandAllCws"/>
 | 
						<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">
 | 
							<header :class="$style.noteHeader">
 | 
				
			||||||
			<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
 | 
								<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
 | 
				
			||||||
			<div style="display: flex; align-items: center; white-space: nowrap; overflow: hidden;">
 | 
								<div style="display: flex; align-items: center; white-space: nowrap; overflow: hidden;">
 | 
				
			||||||
| 
						 | 
					@ -228,7 +228,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<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 mfm from '@sharkey/sfm-js';
 | 
				
			||||||
import * as Misskey from 'misskey-js';
 | 
					import * as Misskey from 'misskey-js';
 | 
				
			||||||
import SkNoteSub from '@/components/SkNoteSub.vue';
 | 
					import SkNoteSub from '@/components/SkNoteSub.vue';
 | 
				
			||||||
| 
						 | 
					@ -301,6 +301,7 @@ const isRenote = (
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const el = shallowRef<HTMLElement>();
 | 
					const el = shallowRef<HTMLElement>();
 | 
				
			||||||
 | 
					const noteEl = shallowRef<HTMLElement>();
 | 
				
			||||||
const menuButton = shallowRef<HTMLElement>();
 | 
					const menuButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const menuVersionsButton = shallowRef<HTMLElement>();
 | 
					const menuVersionsButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const renoteButton = shallowRef<HTMLElement>();
 | 
					const renoteButton = shallowRef<HTMLElement>();
 | 
				
			||||||
| 
						 | 
					@ -731,11 +732,11 @@ function showRenoteMenu(viaKeyboard = false): void {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function focus() {
 | 
					function focus() {
 | 
				
			||||||
	el.value.focus();
 | 
						noteEl.value?.focus();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function blur() {
 | 
					function blur() {
 | 
				
			||||||
	el.value.blur();
 | 
						noteEl.value?.blur();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const repliesLoaded = ref(false);
 | 
					const repliesLoaded = ref(false);
 | 
				
			||||||
| 
						 | 
					@ -776,6 +777,7 @@ function loadConversation() {
 | 
				
			||||||
		noteId: appearNote.value.replyId,
 | 
							noteId: appearNote.value.replyId,
 | 
				
			||||||
	}).then(res => {
 | 
						}).then(res => {
 | 
				
			||||||
		conversation.value = res.reverse();
 | 
							conversation.value = res.reverse();
 | 
				
			||||||
 | 
							focus();
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -792,6 +794,31 @@ function animatedMFM() {
 | 
				
			||||||
		}).then((res) => { if (!res.canceled) allowAnim.value = true; });
 | 
							}).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>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" module>
 | 
					<style lang="scss" module>
 | 
				
			||||||
| 
						 | 
					@ -863,6 +890,7 @@ function animatedMFM() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.note {
 | 
					.note {
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
	padding: 32px;
 | 
						padding: 32px;
 | 
				
			||||||
	font-size: 1.2em;
 | 
						font-size: 1.2em;
 | 
				
			||||||
	overflow: hidden;
 | 
						overflow: hidden;
 | 
				
			||||||
| 
						 | 
					@ -870,6 +898,28 @@ function animatedMFM() {
 | 
				
			||||||
	&:hover > .main > .footer > .button {
 | 
						&:hover > .main > .footer > .button {
 | 
				
			||||||
		opacity: 1;
 | 
							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 {
 | 
					.noteHeader {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -461,7 +461,27 @@ if (props.detail) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.main {
 | 
					.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 {
 | 
					.colorBar {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,7 +78,7 @@ export class Router extends EventEmitter<{
 | 
				
			||||||
	public current: Resolved;
 | 
						public current: Resolved;
 | 
				
			||||||
	public currentRef: ShallowRef<Resolved> = shallowRef();
 | 
						public currentRef: ShallowRef<Resolved> = shallowRef();
 | 
				
			||||||
	public currentRoute: ShallowRef<RouteDef> = shallowRef();
 | 
						public currentRoute: ShallowRef<RouteDef> = shallowRef();
 | 
				
			||||||
	private currentPath: string;
 | 
						private currentPath = '';
 | 
				
			||||||
	private isLoggedIn: boolean;
 | 
						private isLoggedIn: boolean;
 | 
				
			||||||
	private notFoundPageComponent: Component;
 | 
						private notFoundPageComponent: Component;
 | 
				
			||||||
	private currentKey = Date.now().toString();
 | 
						private currentKey = Date.now().toString();
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ export class Router extends EventEmitter<{
 | 
				
			||||||
		super();
 | 
							super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.routes = routes;
 | 
							this.routes = routes;
 | 
				
			||||||
		this.currentPath = currentPath;
 | 
							//this.currentPath = currentPath;
 | 
				
			||||||
		this.isLoggedIn = isLoggedIn;
 | 
							this.isLoggedIn = isLoggedIn;
 | 
				
			||||||
		this.notFoundPageComponent = notFoundPageComponent;
 | 
							this.notFoundPageComponent = notFoundPageComponent;
 | 
				
			||||||
		this.navigate(currentPath, null, false);
 | 
							this.navigate(currentPath, null, false);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -545,12 +545,48 @@ export const mainRouter = new Router(routes, location.pathname + location.search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);
 | 
					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 => {
 | 
					mainRouter.addListener('push', ctx => {
 | 
				
			||||||
	window.history.pushState({ key: ctx.key }, '', ctx.path);
 | 
						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) => {
 | 
					window.addEventListener('popstate', (event) => {
 | 
				
			||||||
	mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
 | 
						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 {
 | 
					export function useRouter(): Router {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue