@lincyaw/viewer 1.0.0-rc.0 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/package.json +20 -8
- package/src/client/block.ts +70 -0
- package/src/client/code/code-block.ts +54 -0
- package/src/client/code/code-editor.ts +73 -0
- package/src/client/code/code-view.ts +22 -0
- package/src/{highlight.ts → client/code/highlight.ts} +2 -2
- package/src/{outputs.ts → client/code/outputs.ts} +23 -36
- package/src/client/content-marks.ts +15 -0
- package/src/client/element.ts +27 -0
- package/src/client/index.ts +40 -0
- package/src/client/layout.ts +22 -0
- package/src/{markdown.ts → client/markdown/handlers.ts} +22 -116
- package/src/client/markdown/hast.ts +20 -0
- package/src/client/markdown/layout-handlers.ts +57 -0
- package/src/client/markdown/math.ts +10 -0
- package/src/client/markdown/mermaid.ts +75 -0
- package/src/client/markdown/render.ts +79 -0
- package/src/client/markdown/role-mark.ts +9 -0
- package/src/client/markdown/tables.ts +49 -0
- package/src/client/markdown/tabs.ts +52 -0
- package/src/client/markdown/tree-cache.ts +77 -0
- package/src/client/operations.ts +9 -0
- package/src/client/registry.ts +21 -0
- package/src/client/section-context.ts +5 -0
- package/src/client/section.ts +30 -0
- package/src/client/slidev-compat/click-directives.ts +16 -0
- package/src/client/slidev-compat/slide-globals.ts +3 -0
- package/src/{style.css → client/style.css} +306 -81
- package/src/client/viewer-messages.ts +7 -0
- package/src/{plugins.ts → client/viewer-plugins.ts} +3 -2
- package/src/client/viewer-state.ts +47 -0
- package/src/client/views/actions.ts +52 -0
- package/src/client/views/controls.css +44 -0
- package/src/client/views/document.ts +12 -0
- package/src/client/views/outlets.ts +37 -0
- package/src/client/views/overlay.ts +16 -0
- package/src/client/views/present.ts +132 -0
- package/src/client/views/presenter.ts +76 -0
- package/src/client/views/scrolly.ts +4 -0
- package/src/client/views/slide.css +8 -0
- package/src/client/views/slide.ts +57 -0
- package/src/domain/clicks.ts +1 -1
- package/src/domain/code-width.ts +11 -0
- package/src/domain/document-content.ts +6 -0
- package/src/domain/{text.ts → footnotes.ts} +0 -12
- package/src/domain/index.ts +5 -4
- package/src/domain/live.ts +11 -10
- package/src/domain/messages.ts +54 -30
- package/src/domain/origin.ts +17 -32
- package/src/domain/outputs.ts +27 -0
- package/src/domain/steps.ts +2 -1
- package/src/block.ts +0 -122
- package/src/cards.ts +0 -130
- package/src/clicks.ts +0 -124
- package/src/code.ts +0 -235
- package/src/context.ts +0 -16
- package/src/dark.ts +0 -5
- package/src/domain/follow.ts +0 -32
- package/src/globals.ts +0 -66
- package/src/i18n.ts +0 -7
- package/src/index.ts +0 -55
- package/src/operations.ts +0 -28
- package/src/present.ts +0 -327
- package/src/registry.ts +0 -81
- package/src/rich.ts +0 -155
- package/src/section.ts +0 -209
- package/src/viewer.ts +0 -116
- package/src/views.ts +0 -101
- /package/src/{env.d.ts → client/env.d.ts} +0 -0
package/src/section.ts
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { isChapterTitle, narrationAt } from '@lincyaw/kernel'
|
|
2
|
-
import type { IRBlock, IRSection } from '@lincyaw/kernel'
|
|
3
|
-
import type { InjectionKey, PropType, Ref, VNodeChild } from 'vue'
|
|
4
|
-
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, shallowRef, toRef } from 'vue'
|
|
5
|
-
import { BlockView } from './block.ts'
|
|
6
|
-
import { SECTION_CONTEXT } from './context.ts'
|
|
7
|
-
import { FINAL_STEP, sectionDiagnostics, stepAtLine } from './domain/index.ts'
|
|
8
|
-
import type { Position } from './domain/index.ts'
|
|
9
|
-
import { messages } from './i18n.ts'
|
|
10
|
-
import { useViewer } from './viewer.ts'
|
|
11
|
-
|
|
12
|
-
export type SectionLayout = 'document' | 'scrolly'
|
|
13
|
-
|
|
14
|
-
export const STEP_LINE = 0.6
|
|
15
|
-
|
|
16
|
-
const READ_LINE = 0.25
|
|
17
|
-
|
|
18
|
-
interface Tracked {
|
|
19
|
-
el: HTMLElement
|
|
20
|
-
id(): string
|
|
21
|
-
layout(): SectionLayout
|
|
22
|
-
step: Ref<number>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface PageTracker {
|
|
26
|
-
add(section: Tracked): () => void
|
|
27
|
-
scrollTo(position: Position): boolean
|
|
28
|
-
hold(): () => void
|
|
29
|
-
dispose(): void
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const PAGE_TRACKER: InjectionKey<PageTracker> = Symbol('presmium.viewer.page')
|
|
33
|
-
|
|
34
|
-
function markers(el: HTMLElement): HTMLElement[] {
|
|
35
|
-
return [...el.querySelectorAll<HTMLElement>('.pm-step')]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function createPageTracker(report: (section: string, step?: number) => void, active: () => boolean): PageTracker {
|
|
39
|
-
const tracked = new Set<Tracked>()
|
|
40
|
-
let frame = 0
|
|
41
|
-
let restoredY: number | undefined
|
|
42
|
-
let pinned: Position | undefined
|
|
43
|
-
const resized = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(() => schedule())
|
|
44
|
-
const scrolled = () => restoredY !== undefined && Math.abs(window.scrollY - restoredY) >= 2
|
|
45
|
-
|
|
46
|
-
const update = () => {
|
|
47
|
-
frame = 0
|
|
48
|
-
const steps = window.innerHeight * STEP_LINE
|
|
49
|
-
const line = window.innerHeight * READ_LINE
|
|
50
|
-
let reading: { section: Tracked, top: number } | undefined
|
|
51
|
-
let first: { section: Tracked, top: number } | undefined
|
|
52
|
-
for (const section of tracked) {
|
|
53
|
-
if (section.layout() === 'scrolly') {
|
|
54
|
-
section.step.value = stepAtLine(markers(section.el).map(m => ({ step: Number(m.dataset.step), top: m.getBoundingClientRect().top })), steps)
|
|
55
|
-
}
|
|
56
|
-
const top = section.el.getBoundingClientRect().top
|
|
57
|
-
if (top <= line && (!reading || top > reading.top)) reading = { section, top }
|
|
58
|
-
if (!first || top < first.top) first = { section, top }
|
|
59
|
-
}
|
|
60
|
-
if (restoredY !== undefined && !scrolled()) {
|
|
61
|
-
if (pinned) place(pinned)
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
restoredY = undefined
|
|
65
|
-
pinned = undefined
|
|
66
|
-
if (!active()) return
|
|
67
|
-
const current = (reading ?? first)?.section
|
|
68
|
-
if (current) report(current.id(), current.layout() === 'scrolly' ? current.step.value : undefined)
|
|
69
|
-
}
|
|
70
|
-
const schedule = () => {
|
|
71
|
-
frame ||= requestAnimationFrame(update)
|
|
72
|
-
}
|
|
73
|
-
const place = (position: Position): boolean => {
|
|
74
|
-
const section = [...tracked].find(s => s.id() === position.section)
|
|
75
|
-
if (!section) return false
|
|
76
|
-
const marker = section.layout() === 'scrolly'
|
|
77
|
-
? markers(section.el).filter(m => Number(m.dataset.step) <= position.step).at(-1)
|
|
78
|
-
: undefined
|
|
79
|
-
const top = marker
|
|
80
|
-
? window.scrollY + marker.getBoundingClientRect().top - window.innerHeight * STEP_LINE + 1
|
|
81
|
-
: window.scrollY + section.el.getBoundingClientRect().top - Number.parseFloat(getComputedStyle(section.el).scrollMarginTop || '0')
|
|
82
|
-
if (Math.abs(window.scrollY - Math.max(top, 0)) >= 1) window.scrollTo({ top, behavior: 'instant' })
|
|
83
|
-
restoredY = window.scrollY
|
|
84
|
-
return true
|
|
85
|
-
}
|
|
86
|
-
const dispose = () => {
|
|
87
|
-
cancelAnimationFrame(frame)
|
|
88
|
-
frame = 0
|
|
89
|
-
resized?.disconnect()
|
|
90
|
-
window.removeEventListener('scroll', schedule)
|
|
91
|
-
window.removeEventListener('resize', schedule)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
add(section) {
|
|
96
|
-
if (tracked.size === 0) {
|
|
97
|
-
window.addEventListener('scroll', schedule, { passive: true })
|
|
98
|
-
window.addEventListener('resize', schedule)
|
|
99
|
-
}
|
|
100
|
-
tracked.add(section)
|
|
101
|
-
resized?.observe(section.el)
|
|
102
|
-
schedule()
|
|
103
|
-
return () => {
|
|
104
|
-
tracked.delete(section)
|
|
105
|
-
resized?.unobserve(section.el)
|
|
106
|
-
if (tracked.size === 0) dispose()
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
hold() {
|
|
110
|
-
const line = window.innerHeight * STEP_LINE
|
|
111
|
-
let reading: { section: Tracked, top: number } | undefined
|
|
112
|
-
for (const section of tracked) {
|
|
113
|
-
const top = section.el.getBoundingClientRect().top
|
|
114
|
-
if (top <= line && (!reading || top > reading.top)) reading = { section, top }
|
|
115
|
-
}
|
|
116
|
-
if (!reading) return () => {}
|
|
117
|
-
const { el } = reading.section
|
|
118
|
-
const above = (candidates: HTMLElement[]) => candidates.filter(c => c.getBoundingClientRect().top <= line).at(-1)
|
|
119
|
-
const anchor = reading.section.layout() === 'scrolly'
|
|
120
|
-
? above(markers(el)) ?? el
|
|
121
|
-
: [...el.querySelectorAll<HTMLElement>('.pm-block')].find(b => b.getBoundingClientRect().top <= line && b.getBoundingClientRect().bottom > line) ?? el
|
|
122
|
-
const top = anchor.getBoundingClientRect().top
|
|
123
|
-
return () => {
|
|
124
|
-
const moved = anchor.isConnected ? anchor.getBoundingClientRect().top - top : 0
|
|
125
|
-
if (Math.abs(moved) < 1) return
|
|
126
|
-
window.scrollBy({ top: moved, behavior: 'instant' })
|
|
127
|
-
restoredY = window.scrollY
|
|
128
|
-
schedule()
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
scrollTo(position) {
|
|
132
|
-
if (!place(position)) return false
|
|
133
|
-
pinned = position
|
|
134
|
-
schedule()
|
|
135
|
-
return true
|
|
136
|
-
},
|
|
137
|
-
dispose,
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function blockViews(blocks: IRBlock[], section: IRSection, step: number, expanded: boolean): VNodeChild[] {
|
|
142
|
-
return blocks.map(block => h(BlockView, { key: block.id, block, section, step, expanded }))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
export const SectionView = defineComponent({
|
|
147
|
-
name: 'PmSectionView',
|
|
148
|
-
props: {
|
|
149
|
-
section: { type: Object as PropType<IRSection>, required: true },
|
|
150
|
-
layout: { type: String as PropType<SectionLayout>, default: 'document' },
|
|
151
|
-
},
|
|
152
|
-
setup(props) {
|
|
153
|
-
const viewer = useViewer()
|
|
154
|
-
const tracker = inject(PAGE_TRACKER, undefined)
|
|
155
|
-
const el = shallowRef<HTMLElement>()
|
|
156
|
-
const scrolled = ref(0)
|
|
157
|
-
const step = computed(() => (props.layout === 'scrolly' ? scrolled.value : FINAL_STEP))
|
|
158
|
-
provide(SECTION_CONTEXT, { section: toRef(props, 'section'), step, view: viewer.pageView })
|
|
159
|
-
|
|
160
|
-
let untrack: (() => void) | undefined
|
|
161
|
-
onMounted(() => {
|
|
162
|
-
if (!el.value || !tracker) return
|
|
163
|
-
untrack = tracker.add({ el: el.value, id: () => props.section.id, layout: () => props.layout, step: scrolled })
|
|
164
|
-
})
|
|
165
|
-
onBeforeUnmount(() => untrack?.())
|
|
166
|
-
|
|
167
|
-
const heading = computed(() => (isChapterTitle(viewer.doc.value, props.section) ? 'h1' : 'h2'))
|
|
168
|
-
|
|
169
|
-
return () => {
|
|
170
|
-
const { section, layout } = props
|
|
171
|
-
const t = messages().section
|
|
172
|
-
const current = step.value
|
|
173
|
-
const problems = viewer.options.diagnostics ? sectionDiagnostics(section, viewer.doc.value.diagnostics) : []
|
|
174
|
-
const body = layout === 'scrolly' && section.blocks.some(b => b.role === 'visual')
|
|
175
|
-
? [
|
|
176
|
-
h('div', { class: 'pm-stage pm-board' }, blockViews(section.blocks.filter(b => b.role === 'visual'), section, current, false)),
|
|
177
|
-
h('div', { class: 'pm-track' }, Array.from({ length: section.steps + 1 }, (_, k) => {
|
|
178
|
-
const narration = narrationAt(section, k)
|
|
179
|
-
return h('div', { 'key': k, 'class': ['pm-step', { 'pm-step--current': k === current, 'pm-step--bare': narration.length === 0 }], 'data-step': k }, blockViews(narration, section, current, false))
|
|
180
|
-
})),
|
|
181
|
-
]
|
|
182
|
-
: [h('div', { class: 'pm-section__body' }, blockViews(section.blocks, section, current, true))]
|
|
183
|
-
return h('section', {
|
|
184
|
-
'ref': el,
|
|
185
|
-
'class': ['pm-section', `pm-section--${layout}`, { 'pm-section--error': problems.some(p => p.severity === 'error') }],
|
|
186
|
-
'data-section': section.id,
|
|
187
|
-
'data-index': section.index,
|
|
188
|
-
'data-step': layout === 'scrolly' ? current : undefined,
|
|
189
|
-
}, [
|
|
190
|
-
h(heading.value, { id: section.id, class: 'pm-section__title', tabindex: -1 }, [
|
|
191
|
-
section.title,
|
|
192
|
-
viewer.options.sectionHeading?.(section, current) ?? null,
|
|
193
|
-
h('a', { 'class': 'header-anchor', 'href': `#${section.id}`, 'aria-label': t.anchor(section.title) }, ''),
|
|
194
|
-
]),
|
|
195
|
-
viewer.options.openSource
|
|
196
|
-
? h('p', { class: 'pm-section__source' }, h('button', {
|
|
197
|
-
type: 'button',
|
|
198
|
-
title: t.sourceTitle(`${section.source.file}:${section.source.line}`),
|
|
199
|
-
onClick: () => viewer.options.openSource!(section.source),
|
|
200
|
-
}, t.openSource))
|
|
201
|
-
: null,
|
|
202
|
-
problems.length
|
|
203
|
-
? h('ul', { class: 'pm-diagnostics', role: 'alert' }, problems.map((p, i) => h('li', { 'key': i, 'data-severity': p.severity }, `${p.severity}: ${p.message}`)))
|
|
204
|
-
: null,
|
|
205
|
-
...body,
|
|
206
|
-
])
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
})
|
package/src/viewer.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { joinWindows } from '@lincyaw/host'
|
|
2
|
-
import type { IRDocument, IRSection, SourcePos } from '@lincyaw/kernel'
|
|
3
|
-
import type { App, InjectionKey, Ref, ShallowRef, VNodeChild } from 'vue'
|
|
4
|
-
import { computed, inject, ref, shallowRef } from 'vue'
|
|
5
|
-
import { applyUpdate, clampPosition, footnoteSections, keepPosition, nextPosition, prevPosition } from './domain/index.ts'
|
|
6
|
-
import type { LiveUpdate, Position } from './domain/index.ts'
|
|
7
|
-
import { getView } from './registry.ts'
|
|
8
|
-
|
|
9
|
-
export interface ViewerOptions {
|
|
10
|
-
renderer?: string
|
|
11
|
-
sectionHref?(id: string): string
|
|
12
|
-
sectionHeading?(section: IRSection, step: number): VNodeChild
|
|
13
|
-
diagnostics?: boolean
|
|
14
|
-
navigate?(position: Position): void
|
|
15
|
-
openPresenter?(position: Position): void
|
|
16
|
-
openSource?(source: SourcePos): void
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface Viewer {
|
|
20
|
-
readonly doc: Readonly<ShallowRef<IRDocument>>
|
|
21
|
-
readonly view: Readonly<Ref<string>>
|
|
22
|
-
readonly pageView: Readonly<Ref<string>>
|
|
23
|
-
readonly position: Readonly<ShallowRef<Position>>
|
|
24
|
-
readonly restoring: Ref<boolean>
|
|
25
|
-
readonly options: ViewerOptions & { renderer: string }
|
|
26
|
-
section(id: string): IRSection | undefined
|
|
27
|
-
footnoteHref(identifier: string): string
|
|
28
|
-
go(position: Position, remote?: boolean): void
|
|
29
|
-
show(position: Position): void
|
|
30
|
-
next(): boolean
|
|
31
|
-
prev(): boolean
|
|
32
|
-
setView(id: string): void
|
|
33
|
-
update(update: LiveUpdate): void
|
|
34
|
-
replace(doc: IRDocument): void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const VIEWER: InjectionKey<Viewer> = Symbol('presmium.viewer')
|
|
38
|
-
|
|
39
|
-
export function footnoteId(identifier: string): string {
|
|
40
|
-
return `fn-${identifier}`
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const CHANNEL = 'presmium.viewer.position'
|
|
44
|
-
|
|
45
|
-
export function createViewer(initial: IRDocument, options: ViewerOptions = {}): Viewer {
|
|
46
|
-
const doc = shallowRef(initial)
|
|
47
|
-
const view = ref('document')
|
|
48
|
-
const pageView = ref('document')
|
|
49
|
-
const position = shallowRef<Position>(clampPosition(initial, { section: initial.sections[0]?.id ?? '', step: 0 }))
|
|
50
|
-
const restoring = ref(false)
|
|
51
|
-
const presenting = () => getView(view.value)?.kind === 'overlay'
|
|
52
|
-
const footnotes = computed(() => footnoteSections(doc.value))
|
|
53
|
-
const windows = joinWindows(`${CHANNEL}:${initial.meta.title}`, (value) => {
|
|
54
|
-
if (presenting()) viewer.go(value as Position, true)
|
|
55
|
-
}, () => undefined)
|
|
56
|
-
const replace = (next: IRDocument) => {
|
|
57
|
-
const kept = keepPosition(doc.value, next, position.value)
|
|
58
|
-
doc.value = next
|
|
59
|
-
if (kept.section !== position.value.section || kept.step !== position.value.step) position.value = kept
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const viewer: Viewer = {
|
|
63
|
-
doc,
|
|
64
|
-
view,
|
|
65
|
-
pageView,
|
|
66
|
-
position,
|
|
67
|
-
restoring,
|
|
68
|
-
options: { ...options, renderer: options.renderer ?? 'web' },
|
|
69
|
-
section: id => doc.value.sections.find(s => s.id === id),
|
|
70
|
-
footnoteHref(identifier) {
|
|
71
|
-
const anchor = `#${footnoteId(identifier)}`
|
|
72
|
-
const section = footnotes.value.get(identifier)
|
|
73
|
-
return section && options.sectionHref ? `${options.sectionHref(section).split('#')[0]}${anchor}` : anchor
|
|
74
|
-
},
|
|
75
|
-
go(target, remote = false) {
|
|
76
|
-
const next = clampPosition(doc.value, target)
|
|
77
|
-
if (next.section === position.value.section && next.step === position.value.step) return
|
|
78
|
-
position.value = next
|
|
79
|
-
if (!remote && presenting()) windows?.send(next)
|
|
80
|
-
},
|
|
81
|
-
show(target) {
|
|
82
|
-
viewer.go(target)
|
|
83
|
-
if (!presenting()) restoring.value = true
|
|
84
|
-
},
|
|
85
|
-
next() {
|
|
86
|
-
const next = nextPosition(doc.value, position.value)
|
|
87
|
-
if (next) viewer.go(next)
|
|
88
|
-
return Boolean(next)
|
|
89
|
-
},
|
|
90
|
-
prev() {
|
|
91
|
-
const prev = prevPosition(doc.value, position.value)
|
|
92
|
-
if (prev) viewer.go(prev)
|
|
93
|
-
return Boolean(prev)
|
|
94
|
-
},
|
|
95
|
-
setView(id) {
|
|
96
|
-
const target = getView(id)
|
|
97
|
-
if (!target || id === view.value) return
|
|
98
|
-
view.value = id
|
|
99
|
-
if (target.kind === 'page') pageView.value = id
|
|
100
|
-
restoring.value = true
|
|
101
|
-
},
|
|
102
|
-
update: update => replace(applyUpdate(doc.value, update)),
|
|
103
|
-
replace,
|
|
104
|
-
}
|
|
105
|
-
return viewer
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function provideViewer(app: App, viewer: Viewer): void {
|
|
109
|
-
app.provide(VIEWER, viewer)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function useViewer(): Viewer {
|
|
113
|
-
const viewer = inject(VIEWER, undefined)
|
|
114
|
-
if (!viewer) throw new Error('[presmium] no viewer: call installViewer(app, viewer) first')
|
|
115
|
-
return viewer
|
|
116
|
-
}
|
package/src/views.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import type { IRSection } from '@lincyaw/kernel'
|
|
2
|
-
import type { PropType } from 'vue'
|
|
3
|
-
import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, provide, watch } from 'vue'
|
|
4
|
-
import { decoded } from './markdown.ts'
|
|
5
|
-
import { getView } from './registry.ts'
|
|
6
|
-
import { createPageTracker, PAGE_TRACKER, SectionView } from './section.ts'
|
|
7
|
-
import type { SectionLayout } from './section.ts'
|
|
8
|
-
import { useViewer } from './viewer.ts'
|
|
9
|
-
|
|
10
|
-
const sectionsProp = { sections: { type: Array as PropType<IRSection[]>, required: true } } as const
|
|
11
|
-
|
|
12
|
-
export const DocumentView = defineComponent({
|
|
13
|
-
name: 'PmDocumentView',
|
|
14
|
-
props: sectionsProp,
|
|
15
|
-
setup(props) {
|
|
16
|
-
const viewer = useViewer()
|
|
17
|
-
const layout = (section: IRSection): SectionLayout => (section.hints[viewer.options.renderer]?.layout === 'scrolly' ? 'scrolly' : 'document')
|
|
18
|
-
return () => h('div', { class: 'pm-view pm-view--document' }, props.sections.map(section => h(SectionView, { key: section.id, section, layout: layout(section) })))
|
|
19
|
-
},
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
export const ScrollyView = defineComponent({
|
|
23
|
-
name: 'PmScrollyView',
|
|
24
|
-
props: sectionsProp,
|
|
25
|
-
setup(props) {
|
|
26
|
-
return () => h('div', { class: 'pm-view pm-view--scrolly' }, props.sections.map(section => h(SectionView, { key: section.id, section, layout: 'scrolly' })))
|
|
27
|
-
},
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const frame = () => new Promise(resolve => requestAnimationFrame(resolve))
|
|
31
|
-
|
|
32
|
-
export const ViewHost = defineComponent({
|
|
33
|
-
name: 'PmViewHost',
|
|
34
|
-
props: {
|
|
35
|
-
sections: { type: Array as PropType<string[]>, required: true },
|
|
36
|
-
},
|
|
37
|
-
setup(props) {
|
|
38
|
-
const viewer = useViewer()
|
|
39
|
-
const pageView = computed(() => getView(viewer.pageView.value) ?? getView('document'))
|
|
40
|
-
const isPage = () => getView(viewer.view.value)?.kind === 'page'
|
|
41
|
-
const tracker = createPageTracker((section, step) => {
|
|
42
|
-
const { position } = viewer
|
|
43
|
-
viewer.go({ section, step: step ?? (section === position.value.section ? position.value.step : 0) })
|
|
44
|
-
}, () => isPage() && !viewer.restoring.value)
|
|
45
|
-
provide(PAGE_TRACKER, tracker)
|
|
46
|
-
onBeforeUnmount(() => tracker.dispose())
|
|
47
|
-
|
|
48
|
-
const linked = () => {
|
|
49
|
-
const hash = decoded(location.hash.slice(1))
|
|
50
|
-
return props.sections.includes(hash) ? hash : undefined
|
|
51
|
-
}
|
|
52
|
-
const onHash = () => {
|
|
53
|
-
const section = linked()
|
|
54
|
-
if (section && isPage()) viewer.show({ section, step: 0 })
|
|
55
|
-
}
|
|
56
|
-
onMounted(() => {
|
|
57
|
-
window.addEventListener('hashchange', onHash)
|
|
58
|
-
if (viewer.restoring.value || props.sections.length === 0) return
|
|
59
|
-
const section = linked()
|
|
60
|
-
if (section) viewer.show({ section, step: 0 })
|
|
61
|
-
else if (!props.sections.includes(viewer.position.value.section)) viewer.go({ section: props.sections[0]!, step: 0 })
|
|
62
|
-
})
|
|
63
|
-
onBeforeUnmount(() => window.removeEventListener('hashchange', onHash))
|
|
64
|
-
|
|
65
|
-
watch(viewer.doc, async () => {
|
|
66
|
-
if (!isPage() || viewer.restoring.value) return
|
|
67
|
-
const restore = tracker.hold()
|
|
68
|
-
await nextTick()
|
|
69
|
-
restore()
|
|
70
|
-
await frame()
|
|
71
|
-
restore()
|
|
72
|
-
}, { flush: 'pre' })
|
|
73
|
-
|
|
74
|
-
watch([viewer.restoring, viewer.view], async () => {
|
|
75
|
-
if (!viewer.restoring.value || !isPage()) return
|
|
76
|
-
await nextTick()
|
|
77
|
-
await frame()
|
|
78
|
-
await frame()
|
|
79
|
-
const { position } = viewer
|
|
80
|
-
if (tracker.scrollTo(position.value)) viewer.restoring.value = false
|
|
81
|
-
else if (!props.sections.includes(position.value.section) && viewer.options.navigate) viewer.options.navigate(position.value)
|
|
82
|
-
else viewer.restoring.value = false
|
|
83
|
-
}, { immediate: true })
|
|
84
|
-
|
|
85
|
-
const sections = computed(() => props.sections.flatMap(id => viewer.section(id) ?? []))
|
|
86
|
-
return () => h('div', { 'class': 'pm-view-host', 'aria-busy': viewer.restoring.value ? 'true' : undefined }, [
|
|
87
|
-
pageView.value ? h(pageView.value.component, { sections: sections.value }) : null,
|
|
88
|
-
])
|
|
89
|
-
},
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
export const OverlayHost = defineComponent({
|
|
93
|
-
name: 'PmOverlayHost',
|
|
94
|
-
setup() {
|
|
95
|
-
const viewer = useViewer()
|
|
96
|
-
return () => {
|
|
97
|
-
const view = getView(viewer.view.value)
|
|
98
|
-
return view?.kind === 'overlay' ? h(view.component, { key: view.id }) : null
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
})
|
|
File without changes
|