From 87c56488fc5d67bdfd9b31938c91d2e432096c74 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 25 Feb 2024 19:43:33 +0100 Subject: [PATCH] feat: improve note clicks experience --- packages/client/internals/NoteDisplay.vue | 48 +++++++++++++++++------ packages/client/pages/notes.vue | 1 + packages/client/pages/overview.vue | 2 +- packages/client/pages/presenter.vue | 6 +-- packages/client/styles/index.css | 13 +++++- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/packages/client/internals/NoteDisplay.vue b/packages/client/internals/NoteDisplay.vue index cc3f6848d9..0cad3396be 100644 --- a/packages/client/internals/NoteDisplay.vue +++ b/packages/client/internals/NoteDisplay.vue @@ -16,14 +16,18 @@ defineEmits(['click']) const withClicks = computed(() => props.clicksContext?.current != null && props.noteHtml?.includes('slidev-note-click-mark')) const noteDisplay = ref(null) +const CLASS_FADE = 'slidev-note-fade' +const CLASS_MARKER = 'slidev-note-click-mark' + function highlightNote() { if (!noteDisplay.value || !withClicks.value || props.clicksContext?.current == null) return - const disabled = +props.clicksContext?.current < 0 || +props.clicksContext?.current >= CLICKS_MAX + const current = +props.clicksContext?.current ?? CLICKS_MAX + const disabled = current < 0 || current >= CLICKS_MAX if (disabled) { Array.from(noteDisplay.value.querySelectorAll('*')) - .forEach(el => el.classList.remove('slidev-note-fade')) + .forEach(el => el.classList.remove(CLASS_FADE)) return } @@ -36,10 +40,14 @@ function highlightNote() { ignoreParent(node.parentElement) } - const markers = Array.from(noteDisplay.value.querySelectorAll('.slidev-note-click-mark')) + const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[] + const markersMap = new Map() + // Convert all sibling text nodes to spans, so we attach classes to them for (const marker of markers) { const parent = marker.parentElement! + const clicks = Number(marker.dataset!.clicks) + markersMap.set(clicks, marker) // Ignore the parents of the marker, so the class only applies to the children ignoreParent(parent) Array.from(parent!.childNodes) @@ -56,25 +64,39 @@ function highlightNote() { let count = 0 - const groups = new Map() - + // Segmenting notes by clicks + const segments = new Map() for (const child of children) { - if (!groups.has(count)) - groups.set(count, []) - - groups.get(count)!.push(child) - if (child.classList.contains('slidev-note-click-mark')) + if (!segments.has(count)) + segments.set(count, []) + segments.get(count)!.push(child) + // Update count when reach marker + if (child.classList.contains(CLASS_MARKER)) count = Number((child as HTMLElement).dataset.clicks) || (count + 1) } - for (const [count, els] of groups) { + // Apply + for (const [count, els] of segments) { els.forEach(el => el.classList.toggle( - 'slidev-note-fade', + CLASS_FADE, nodeToIgnores.has(el) ? false - : +count !== +props.clicksContext!.current!, + : count !== current, )) } + + for (const [clicks, marker] of markersMap) { + marker.classList.remove(CLASS_FADE) + marker.classList.toggle(`${CLASS_MARKER}-past`, clicks < current) + marker.classList.toggle(`${CLASS_MARKER}-active`, clicks === current) + marker.classList.toggle(`${CLASS_MARKER}-next`, clicks === current + 1) + marker.classList.toggle(`${CLASS_MARKER}-future`, clicks > current + 1) + marker.addEventListener('dblclick', (e) => { + props.clicksContext!.current = clicks + e.stopPropagation() + e.stopImmediatePropagation() + }) + } } watch( diff --git a/packages/client/pages/notes.vue b/packages/client/pages/notes.vue index 2ebcc29383..65345b7c76 100644 --- a/packages/client/pages/notes.vue +++ b/packages/client/pages/notes.vue @@ -51,6 +51,7 @@ function decreaseFontSize() { :note="currentRoute?.meta?.slide?.note" :note-html="currentRoute?.meta?.slide?.noteHTML" :placeholder="`No notes for Slide ${pageNo}.`" + :clicks-context="currentRoute?.meta?.__clicksContext" />
diff --git a/packages/client/pages/overview.vue b/packages/client/pages/overview.vue index 4cc8790c56..6b60b8bba8 100644 --- a/packages/client/pages/overview.vue +++ b/packages/client/pages/overview.vue @@ -181,7 +181,7 @@ onMounted(() => { class="max-w-250 w-250 text-lg rounded p3" :auto-height="true" :editing="edittingNote === idx" - :clicks="getClicksContext(route).current" + :clicks-context="getClicksContext(route)" @dblclick="edittingNote !== idx ? edittingNote = idx : null" @update:editing="edittingNote = null" /> diff --git a/packages/client/pages/presenter.vue b/packages/client/pages/presenter.vue index 7d388ad78c..6fc05c2737 100644 --- a/packages/client/pages/presenter.vue +++ b/packages/client/pages/presenter.vue @@ -121,7 +121,7 @@ onMounted(() => { class="h-full w-full" > { :no="currentSlideId" class="w-full max-w-full h-full overflow-auto p-2 lg:p-4" :editing="notesEditing" - :clicks="clicksContext.current" + :clicks-context="clicksContext" :style="{ fontSize: `${presenterNotesFontSize}em` }" /> { :no="currentSlideId" class="w-full max-w-full h-full overflow-auto p-2 lg:p-4" :style="{ fontSize: `${presenterNotesFontSize}em` }" - :clicks="clicksContext.current" + :clicks-context="clicksContext" />
diff --git a/packages/client/styles/index.css b/packages/client/styles/index.css index 5ec410b1cb..840b7272a3 100644 --- a/packages/client/styles/index.css +++ b/packages/client/styles/index.css @@ -70,9 +70,20 @@ html { } .slidev-note-click-mark { + user-select: none; font-size: 0.7em; display: inline-flex; - --uno: text-violet bg-violet/10 px1 font-mono rounded items-center; + --uno: text-violet bg-violet/10 px1 font-mono rounded items-center border border-transparent; +} +.slidev-note-click-mark.slidev-note-click-mark-active { + --uno: border border-violet; +} +.slidev-note-click-mark.slidev-note-click-mark-past { + filter: saturate(0); + opacity: 0.5; +} +.slidev-note-click-mark.slidev-note-click-mark-future { + opacity: 0.5; } .slidev-note-click-mark::before {