Skip to content

Commit

Permalink
feat: improve note markers in overview
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Feb 26, 2024
1 parent 97a7f93 commit 4b2f4b4
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 49 deletions.
55 changes: 31 additions & 24 deletions packages/client/internals/NoteDisplay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ const props = defineProps<{
autoScroll?: boolean
}>()
defineEmits(['click'])
const emit = defineEmits<{
(type: 'markerDblclick', e: MouseEvent, clicks: number): void
(type: 'markerClick', e: MouseEvent, clicks: number): void
}>()
const withClicks = computed(() => props.clicksContext?.current != null && props.noteHtml?.includes('slidev-note-click-mark'))
const noteDisplay = ref<HTMLElement | null>(null)
Expand All @@ -21,16 +24,13 @@ const CLASS_FADE = 'slidev-note-fade'
const CLASS_MARKER = 'slidev-note-click-mark'
function highlightNote() {
if (!noteDisplay.value || !withClicks.value || props.clicksContext?.current == null)
if (!noteDisplay.value || !withClicks.value)
return
const current = +props.clicksContext?.current ?? CLICKS_MAX
const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
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(CLASS_FADE))
return
}
const nodeToIgnores = new Set<Element>()
function ignoreParent(node: Element) {
Expand All @@ -41,7 +41,6 @@ function highlightNote() {
ignoreParent(node.parentElement)
}
const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
const markersMap = new Map<number, HTMLElement>()
// Convert all sibling text nodes to spans, so we attach classes to them
Expand Down Expand Up @@ -78,25 +77,36 @@ function highlightNote() {
// Apply
for (const [count, els] of segments) {
els.forEach(el => el.classList.toggle(
CLASS_FADE,
nodeToIgnores.has(el)
? false
: count !== current,
))
if (disabled) {
els.forEach(el => el.classList.remove(CLASS_FADE))
}
else {
els.forEach(el => el.classList.toggle(
CLASS_FADE,
nodeToIgnores.has(el)
? false
: 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) => {
marker.classList.toggle(`${CLASS_MARKER}-past`, disabled ? false : clicks < current)
marker.classList.toggle(`${CLASS_MARKER}-active`, disabled ? false : clicks === current)
marker.classList.toggle(`${CLASS_MARKER}-next`, disabled ? false : clicks === current + 1)
marker.classList.toggle(`${CLASS_MARKER}-future`, disabled ? false : clicks > current + 1)
marker.ondblclick = (e) => {
emit('markerDblclick', e, clicks)
if (e.defaultPrevented)
return
props.clicksContext!.current = clicks
e.stopPropagation()
e.stopImmediatePropagation()
})
}
marker.onclick = (e) => {
emit('markerClick', e, clicks)
}
if (props.autoScroll && clicks === current)
marker.scrollIntoView({ block: 'center', behavior: 'smooth' })
Expand Down Expand Up @@ -124,22 +134,19 @@ onMounted(() => {
ref="noteDisplay"
class="prose overflow-auto outline-none slidev-note"
:class="[props.class, withClicks ? 'slidev-note-with-clicks' : '']"
@click="$emit('click')"
v-html="noteHtml"
/>
<div
v-else-if="note"
class="prose overflow-auto outline-none slidev-note"
:class="props.class"
@click="$emit('click')"
>
<p v-text="note" />
</div>
<div
v-else
class="prose overflow-auto outline-none opacity-50 italic select-none slidev-note"
:class="props.class"
@click="$emit('click')"
>
<p v-text="props.placeholder || 'No notes.'" />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,25 @@ const props = defineProps({
},
})
const emit = defineEmits([
'update:editing',
])
const emit = defineEmits<{
(type: 'update:editing', value: boolean): void
(type: 'markerDblclick', e: MouseEvent, clicks: number): void
(type: 'markerClick', e: MouseEvent, clicks: number): void
}>()
const editing = useVModel(props, 'editing', emit, { passive: true })
const { info, update } = useDynamicSlideInfo(props.no)
const note = ref('')
let timer: any
// Send back the note on changes
const { ignoreUpdates } = ignorableWatch(
note,
(v) => {
if (!editing.value)
return
const id = props.no
clearTimeout(timer)
timer = setTimeout(() => {
Expand All @@ -51,46 +57,44 @@ const { ignoreUpdates } = ignorableWatch(
},
)
// Update note value when info changes
watch(
info,
(v) => {
() => info.value?.note,
(value = '') => {
if (editing.value)
return
clearTimeout(timer)
ignoreUpdates(() => {
note.value = v?.note || ''
note.value = value
})
},
{ immediate: true, flush: 'sync' },
)
const input = ref<HTMLTextAreaElement>()
const inputEl = ref<HTMLTextAreaElement>()
const inputHeight = ref<number | null>()
watchEffect(() => {
if (editing.value)
input.value?.focus()
inputEl.value?.focus()
})
onClickOutside(input, () => {
onClickOutside(inputEl, () => {
editing.value = false
})
function calculateHeight() {
if (!props.autoHeight || !input.value || !editing.value)
function calculateEditorHeight() {
if (!props.autoHeight || !inputEl.value || !editing.value)
return
if (input.value.scrollHeight > input.value.clientHeight)
input.value.style.height = `${input.value.scrollHeight}px`
if (inputEl.value.scrollHeight > inputEl.value.clientHeight)
inputEl.value.style.height = `${inputEl.value.scrollHeight}px`
}
const inputHeight = ref<number | null>()
watch(
note,
() => {
nextTick(() => {
calculateHeight()
})
},
() => nextTick(() => {
calculateEditorHeight()
}),
{ flush: 'post', immediate: true },
)
</script>
Expand All @@ -105,10 +109,12 @@ watch(
:note-html="info?.noteHTML"
:clicks-context="clicksContext"
:auto-scroll="!autoHeight"
@marker-click="(e, clicks) => emit('markerClick', e, clicks)"
@marker-dblclick="(e, clicks) => emit('markerDblclick', e, clicks)"
/>
<textarea
v-else
ref="input"
ref="inputEl"
v-model="note"
class="prose resize-none overflow-auto outline-none bg-transparent block border-primary border-2"
style="line-height: 1.75;"
Expand Down
14 changes: 12 additions & 2 deletions packages/client/pages/overview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import SlideContainer from '../internals/SlideContainer.vue'
import SlideWrapper from '../internals/SlideWrapper'
import DrawingPreview from '../internals/DrawingPreview.vue'
import IconButton from '../internals/IconButton.vue'
import NoteEditor from '../internals/NoteEditor.vue'
import NoteEditable from '../internals/NoteEditable.vue'
import OverviewClicksSlider from '../internals/OverviewClicksSlider.vue'
import { CLICKS_MAX } from '../constants'
Expand Down Expand Up @@ -80,6 +80,15 @@ function scrollToSlide(idx: number) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
function onMarkerClick(e: MouseEvent, clicks: number, route: RouteRecordRaw) {
const ctx = getClicksContext(route)
if (ctx.current === clicks)
ctx.current = CLICKS_MAX
else
ctx.current = clicks
e.preventDefault()
}
onMounted(() => {
nextTick(() => {
checkActiveBlocks()
Expand Down Expand Up @@ -192,14 +201,15 @@ onMounted(() => {
<carbon:pen />
</IconButton>
</div>
<NoteEditor
<NoteEditable
:no="idx"
class="max-w-250 w-250 text-lg rounded p3"
:auto-height="true"
:editing="edittingNote === idx"
:clicks-context="getClicksContext(route)"
@dblclick="edittingNote !== idx ? edittingNote = idx : null"
@update:editing="edittingNote = null"
@marker-click="(e, clicks) => onMarkerClick(e, clicks, route)"
/>
<div
v-if="wordCounts[idx] > 0"
Expand Down
4 changes: 2 additions & 2 deletions packages/client/pages/presenter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import SlideWrapper from '../internals/SlideWrapper'
import SlideContainer from '../internals/SlideContainer.vue'
import NavControls from '../internals/NavControls.vue'
import QuickOverview from '../internals/QuickOverview.vue'
import NoteEditor from '../internals/NoteEditor.vue'
import NoteEditable from '../internals/NoteEditable.vue'
import NoteStatic from '../internals/NoteStatic.vue'
import Goto from '../internals/Goto.vue'
import SlidesShow from '../internals/SlidesShow.vue'
Expand Down Expand Up @@ -130,7 +130,7 @@ onMounted(() => {
<SideEditor />
</div>
<div v-else class="grid-section note grid grid-rows-[1fr_min-content] overflow-hidden">
<NoteEditor
<NoteEditable
v-if="__DEV__"
:key="`edit-${currentSlideId}`"
v-model:editing="notesEditing"
Expand Down

0 comments on commit 4b2f4b4

Please sign in to comment.