@pathmx/core 0.5.0
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/LICENSE.md +172 -0
- package/README.md +111 -0
- package/actions/actions.ts +307 -0
- package/actions/draft.ts +105 -0
- package/actions/transaction.ts +164 -0
- package/assets.ts +149 -0
- package/authority/access.ts +820 -0
- package/authority/actors.ts +271 -0
- package/authority/agents.ts +95 -0
- package/authority/credentials.ts +144 -0
- package/authority/model.ts +71 -0
- package/authority/sharing.ts +89 -0
- package/browser-bundle.ts +61 -0
- package/canonical.ts +41 -0
- package/cli/dev.ts +134 -0
- package/cli/export.ts +74 -0
- package/cli/index.ts +52 -0
- package/cli/lint.ts +164 -0
- package/cli/mv.ts +30 -0
- package/cli/perms.ts +182 -0
- package/cli/plugin-packages.ts +159 -0
- package/cli/plugins.ts +95 -0
- package/cli/query.ts +225 -0
- package/cli/readme.md +95 -0
- package/cli/release.ts +75 -0
- package/cli/rm.ts +22 -0
- package/cli/serve.ts +100 -0
- package/cli/tree.ts +131 -0
- package/cli/update.ts +78 -0
- package/cli/utils.ts +53 -0
- package/core.ts +96 -0
- package/database.ts +189 -0
- package/dependencies.ts +60 -0
- package/dist/pathmx.js +1224 -0
- package/document.ts +194 -0
- package/environment.ts +110 -0
- package/environments/bun-plugins.ts +68 -0
- package/environments/bun-publish.ts +194 -0
- package/environments/bun.ts +223 -0
- package/globals.d.ts +14 -0
- package/graph.ts +153 -0
- package/host/compiler.ts +314 -0
- package/host/create.ts +66 -0
- package/host/engine.ts +630 -0
- package/host/plugin-host.ts +487 -0
- package/host/render.ts +102 -0
- package/host/response.ts +20 -0
- package/host/shell.html +15 -0
- package/host/source-sync.ts +254 -0
- package/html-attributes.ts +15 -0
- package/html.ts +48 -0
- package/icons/LICENSE +43 -0
- package/icons/lucide.ts +54 -0
- package/index.ts +75 -0
- package/invalidation.ts +85 -0
- package/log.ts +84 -0
- package/ops/href.ts +87 -0
- package/ops/index.ts +9 -0
- package/ops/move-source.ts +114 -0
- package/ops/plan.ts +47 -0
- package/ops/remove-source.ts +70 -0
- package/package.json +46 -0
- package/path.ts +75 -0
- package/pattern.ts +314 -0
- package/perf.ts +88 -0
- package/plugins/actor-path.ts +43 -0
- package/plugins/actor-profile.ts +55 -0
- package/plugins/agent-browsing.ts +183 -0
- package/plugins/app-component.ts +23 -0
- package/plugins/block-layout/index.ts +78 -0
- package/plugins/block-layout/theme.css +90 -0
- package/plugins/callout/index.ts +236 -0
- package/plugins/callout/theme.css +138 -0
- package/plugins/canonical-links.ts +144 -0
- package/plugins/code-highlight/client.ts +89 -0
- package/plugins/code-highlight/index.ts +147 -0
- package/plugins/code-highlight/theme.css +89 -0
- package/plugins/context.ts +131 -0
- package/plugins/datatype/Datatype[wdth,wght].woff2 +0 -0
- package/plugins/datatype/OFL.txt +96 -0
- package/plugins/datatype/index.ts +287 -0
- package/plugins/definition-list.ts +236 -0
- package/plugins/directive-definitions.ts +13 -0
- package/plugins/embedding/client.ts +88 -0
- package/plugins/embedding/index.ts +43 -0
- package/plugins/image/index.ts +68 -0
- package/plugins/image/theme.css +43 -0
- package/plugins/include.ts +119 -0
- package/plugins/index.ts +91 -0
- package/plugins/input/authoring.ts +236 -0
- package/plugins/input/config.ts +85 -0
- package/plugins/input/index.ts +359 -0
- package/plugins/input/model.ts +173 -0
- package/plugins/input/state.ts +272 -0
- package/plugins/layout.ts +313 -0
- package/plugins/literate/client.ts +208 -0
- package/plugins/literate/index.ts +742 -0
- package/plugins/math/index.ts +140 -0
- package/plugins/meta.ts +42 -0
- package/plugins/navigation/index.ts +158 -0
- package/plugins/navigation/model.ts +364 -0
- package/plugins/preset.ts +65 -0
- package/plugins/query/index.ts +322 -0
- package/plugins/rss/index.ts +46 -0
- package/plugins/runtime/actions.ts +62 -0
- package/plugins/runtime/client.ts +8 -0
- package/plugins/runtime/document.ts +506 -0
- package/plugins/runtime/error.ts +154 -0
- package/plugins/runtime/heading-links.ts +31 -0
- package/plugins/runtime/index.ts +104 -0
- package/plugins/runtime/live.ts +51 -0
- package/plugins/runtime/navigation.ts +113 -0
- package/plugins/runtime/pending.ts +53 -0
- package/plugins/runtime/publication.ts +19 -0
- package/plugins/runtime/start.ts +177 -0
- package/plugins/runtime/static-client.ts +6 -0
- package/plugins/runtime/static-live.ts +188 -0
- package/plugins/runtime/view-transition.ts +72 -0
- package/plugins/runtime/viewport.ts +110 -0
- package/plugins/schema.ts +24 -0
- package/plugins/scripts.ts +140 -0
- package/plugins/source-preview/client.ts +256 -0
- package/plugins/source-preview/index.ts +132 -0
- package/plugins/source-preview/model.ts +11 -0
- package/plugins/source-preview/preview.css +107 -0
- package/plugins/styles.ts +145 -0
- package/plugins/theme/components.ts +10 -0
- package/plugins/theme/foundation.css +358 -0
- package/plugins/theme/index.ts +333 -0
- package/plugins/theme/prose.css +284 -0
- package/plugins/theme/scheme.js +63 -0
- package/plugins/theme/toggle.css +44 -0
- package/plugins/theme/view-transition.css +68 -0
- package/plugins/toc/client.ts +146 -0
- package/plugins/toc/index.ts +232 -0
- package/plugins/toc/theme.css +172 -0
- package/plugins/types.ts +538 -0
- package/problem.ts +41 -0
- package/project.ts +6 -0
- package/publish/build.ts +402 -0
- package/publish/concurrent.ts +32 -0
- package/publish/identity.ts +63 -0
- package/publish/index.ts +11 -0
- package/publish/location.ts +167 -0
- package/publish/model.ts +58 -0
- package/publish/plan.ts +300 -0
- package/publish/validate.ts +150 -0
- package/query/candidate-index.ts +101 -0
- package/query/dependency-index.ts +99 -0
- package/query/fields.ts +216 -0
- package/query/invalidation.ts +48 -0
- package/query/kernel.ts +776 -0
- package/query/model.ts +79 -0
- package/repository.ts +374 -0
- package/runtime-profile.ts +34 -0
- package/schema/model.ts +92 -0
- package/schema/registry.ts +642 -0
- package/schema/standard.ts +226 -0
- package/server/context.ts +94 -0
- package/server/css-imports.ts +66 -0
- package/server/http.ts +333 -0
- package/server/page.ts +21 -0
- package/server/protocols.ts +12 -0
- package/server/representations.ts +66 -0
- package/server/router.ts +131 -0
- package/server/routes.ts +202 -0
- package/server/serve.ts +5 -0
- package/server/watch.ts +120 -0
- package/source/blocks.ts +71 -0
- package/source/format.ts +164 -0
- package/source/index.ts +67 -0
- package/source/interpolate.ts +63 -0
- package/source/markdown/blocks.ts +46 -0
- package/source/markdown/comments.ts +32 -0
- package/source/markdown/fences.ts +67 -0
- package/source/markdown/headings.ts +88 -0
- package/source/markdown/index.ts +34 -0
- package/source/markdown/inline-code.ts +6 -0
- package/source/markdown/links.ts +326 -0
- package/source/markdown/lists.ts +223 -0
- package/source/markdown/prose.ts +74 -0
- package/source/markdown/scan.ts +79 -0
- package/source/markdown/text.ts +82 -0
- package/source/meta.ts +87 -0
- package/source/source.ts +121 -0
- package/source/topmatter.ts +57 -0
- package/source/utils.ts +21 -0
- package/source-diagnostic.ts +63 -0
- package/text-file.ts +26 -0
- package/util.ts +35 -0
- package/view/components.ts +499 -0
- package/view/model.ts +91 -0
- package/view/projection.ts +162 -0
- package/view/session.ts +146 -0
- package/view/syntax.ts +372 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { LiveSubscription, LiveSubscriptionFactory } from "./live.ts"
|
|
2
|
+
import { publicationPath } from "./publication.ts"
|
|
3
|
+
|
|
4
|
+
const POLL_INTERVAL = 30_000
|
|
5
|
+
const MAX_BACKOFF = 5 * 60_000
|
|
6
|
+
|
|
7
|
+
type BuildRecord = Readonly<{
|
|
8
|
+
version: string
|
|
9
|
+
pages: string
|
|
10
|
+
}>
|
|
11
|
+
|
|
12
|
+
type PageMap = Readonly<{
|
|
13
|
+
version: 1
|
|
14
|
+
build: string
|
|
15
|
+
pages: Readonly<Record<string, string>>
|
|
16
|
+
}>
|
|
17
|
+
|
|
18
|
+
function currentPage() {
|
|
19
|
+
return document.documentElement.dataset.pmxPage
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function currentRoute() {
|
|
23
|
+
const view = document.querySelector<HTMLElement>("[data-pmx-view]")
|
|
24
|
+
return view?.dataset.pmxRoute || location.pathname
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isBuildRecord(value: unknown): value is BuildRecord {
|
|
28
|
+
return (
|
|
29
|
+
typeof value === "object" &&
|
|
30
|
+
value !== null &&
|
|
31
|
+
"version" in value &&
|
|
32
|
+
typeof value.version === "string" &&
|
|
33
|
+
value.version.length > 0 &&
|
|
34
|
+
"pages" in value &&
|
|
35
|
+
typeof value.pages === "string" &&
|
|
36
|
+
value.pages.length > 0
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isPageMap(value: unknown, build: string): value is PageMap {
|
|
41
|
+
if (
|
|
42
|
+
typeof value !== "object" ||
|
|
43
|
+
value === null ||
|
|
44
|
+
!("version" in value) ||
|
|
45
|
+
value.version !== 1 ||
|
|
46
|
+
!("build" in value) ||
|
|
47
|
+
value.build !== build ||
|
|
48
|
+
!("pages" in value) ||
|
|
49
|
+
typeof value.pages !== "object" ||
|
|
50
|
+
value.pages === null
|
|
51
|
+
) {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
return Object.values(value.pages).every(
|
|
55
|
+
(leaf) => typeof leaf === "string" && leaf.length > 0,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function jitter(delay: number) {
|
|
60
|
+
return Math.round(delay * (0.9 + Math.random() * 0.2))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const createPollingLiveSubscription: LiveSubscriptionFactory = (
|
|
64
|
+
onSync,
|
|
65
|
+
): LiveSubscription => {
|
|
66
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
67
|
+
let controller: AbortController | undefined
|
|
68
|
+
let loaded: { record: BuildRecord; map: PageMap } | undefined
|
|
69
|
+
let running = false
|
|
70
|
+
let failures = 0
|
|
71
|
+
|
|
72
|
+
const schedule = (delay = POLL_INTERVAL) => {
|
|
73
|
+
if (!running || timer || document.visibilityState === "hidden") return
|
|
74
|
+
timer = setTimeout(() => {
|
|
75
|
+
timer = undefined
|
|
76
|
+
void check()
|
|
77
|
+
}, jitter(delay))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const loadPageMap = async (record: BuildRecord, signal: AbortSignal) => {
|
|
81
|
+
if (
|
|
82
|
+
loaded?.record.version === record.version &&
|
|
83
|
+
loaded.record.pages === record.pages
|
|
84
|
+
) {
|
|
85
|
+
return loaded.map
|
|
86
|
+
}
|
|
87
|
+
const url = new URL(record.pages, location.href)
|
|
88
|
+
if (url.origin !== location.origin) {
|
|
89
|
+
throw new Error("PathMX page map must use the publication origin.")
|
|
90
|
+
}
|
|
91
|
+
const response = await fetch(url, {
|
|
92
|
+
cache: "force-cache",
|
|
93
|
+
headers: { Accept: "application/json" },
|
|
94
|
+
signal,
|
|
95
|
+
})
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(`Page map returned ${response.status}.`)
|
|
98
|
+
}
|
|
99
|
+
const map: unknown = await response.json()
|
|
100
|
+
if (!isPageMap(map, record.version)) {
|
|
101
|
+
throw new Error("Invalid PathMX page map.")
|
|
102
|
+
}
|
|
103
|
+
loaded = { record, map }
|
|
104
|
+
return map
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const pageIsCurrent = async (record: BuildRecord, signal: AbortSignal) => {
|
|
108
|
+
const map = await loadPageMap(record, signal)
|
|
109
|
+
return map.pages[currentRoute()] === currentPage()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const check = async () => {
|
|
113
|
+
if (!running) return
|
|
114
|
+
if (document.visibilityState === "hidden") return
|
|
115
|
+
|
|
116
|
+
controller?.abort()
|
|
117
|
+
const request = new AbortController()
|
|
118
|
+
controller = request
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch(
|
|
121
|
+
new URL(publicationPath("/.pmx/build.json"), location.origin),
|
|
122
|
+
{
|
|
123
|
+
cache: "no-store",
|
|
124
|
+
headers: { Accept: "application/json" },
|
|
125
|
+
signal: request.signal,
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
throw new Error(`Build check returned ${response.status}.`)
|
|
130
|
+
}
|
|
131
|
+
const record: unknown = await response.json()
|
|
132
|
+
if (!isBuildRecord(record)) {
|
|
133
|
+
throw new Error("Invalid PathMX build record.")
|
|
134
|
+
}
|
|
135
|
+
if (await pageIsCurrent(record, request.signal)) {
|
|
136
|
+
failures = 0
|
|
137
|
+
} else {
|
|
138
|
+
await onSync()
|
|
139
|
+
failures = (await pageIsCurrent(record, request.signal))
|
|
140
|
+
? 0
|
|
141
|
+
: failures + 1
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (!request.signal.aborted) failures++
|
|
145
|
+
} finally {
|
|
146
|
+
if (controller === request) controller = undefined
|
|
147
|
+
const backoff = Math.min(
|
|
148
|
+
POLL_INTERVAL * 2 ** Math.min(failures, 4),
|
|
149
|
+
MAX_BACKOFF,
|
|
150
|
+
)
|
|
151
|
+
schedule(backoff)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const onVisibilityChange = () => {
|
|
156
|
+
if (!running) return
|
|
157
|
+
if (timer) clearTimeout(timer)
|
|
158
|
+
timer = undefined
|
|
159
|
+
if (document.visibilityState !== "visible") {
|
|
160
|
+
controller?.abort()
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
schedule(0)
|
|
164
|
+
}
|
|
165
|
+
document.addEventListener("visibilitychange", onVisibilityChange)
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
bind() {
|
|
169
|
+
if (!running) {
|
|
170
|
+
running = true
|
|
171
|
+
schedule()
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
if (controller) return
|
|
175
|
+
if (timer) clearTimeout(timer)
|
|
176
|
+
timer = undefined
|
|
177
|
+
schedule(0)
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
close() {
|
|
181
|
+
running = false
|
|
182
|
+
if (timer) clearTimeout(timer)
|
|
183
|
+
timer = undefined
|
|
184
|
+
controller?.abort()
|
|
185
|
+
controller = undefined
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const viewTransitionType = {
|
|
2
|
+
navigation: "pmx-navigation",
|
|
3
|
+
traverse: "pmx-traverse",
|
|
4
|
+
forward: "pmx-forward",
|
|
5
|
+
back: "pmx-back",
|
|
6
|
+
} as const
|
|
7
|
+
|
|
8
|
+
export type ViewTransitionType =
|
|
9
|
+
(typeof viewTransitionType)[keyof typeof viewTransitionType]
|
|
10
|
+
|
|
11
|
+
export type ViewTransitionNavigation = {
|
|
12
|
+
type: "navigation" | "traverse"
|
|
13
|
+
currentIndex?: number
|
|
14
|
+
destinationIndex?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type ViewTransitionUpdate = () => void | Promise<void>
|
|
18
|
+
|
|
19
|
+
type StartViewTransition = {
|
|
20
|
+
(update?: ViewTransitionUpdate): ViewTransition
|
|
21
|
+
(options: { update?: ViewTransitionUpdate; types?: string[] }): ViewTransition
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function historyIndex(value: number | undefined) {
|
|
25
|
+
return value == null || value < 0 ? undefined : value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function navigationViewTransitionTypes(
|
|
29
|
+
navigation: ViewTransitionNavigation,
|
|
30
|
+
): readonly ViewTransitionType[] {
|
|
31
|
+
const renderType = viewTransitionType[navigation.type]
|
|
32
|
+
if (navigation.type !== "traverse") {
|
|
33
|
+
return [renderType, viewTransitionType.forward]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const from = historyIndex(navigation.currentIndex)
|
|
37
|
+
const to = historyIndex(navigation.destinationIndex)
|
|
38
|
+
if (from == null || to == null || from === to) return [renderType]
|
|
39
|
+
return [
|
|
40
|
+
renderType,
|
|
41
|
+
to < from ? viewTransitionType.back : viewTransitionType.forward,
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function supportsTypedStart() {
|
|
46
|
+
try {
|
|
47
|
+
return CSS.supports("selector(:active-view-transition-type(pmx-forward))")
|
|
48
|
+
} catch {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function startPathMXViewTransition(
|
|
54
|
+
update: ViewTransitionUpdate,
|
|
55
|
+
types: readonly string[],
|
|
56
|
+
) {
|
|
57
|
+
const start = document.startViewTransition.bind(
|
|
58
|
+
document,
|
|
59
|
+
) as StartViewTransition
|
|
60
|
+
const listed = [...types]
|
|
61
|
+
const transition =
|
|
62
|
+
listed.length && supportsTypedStart()
|
|
63
|
+
? start({ update, types: listed })
|
|
64
|
+
: start(update)
|
|
65
|
+
const set = (
|
|
66
|
+
transition as ViewTransition & { types?: { add(type: string): void } }
|
|
67
|
+
).types
|
|
68
|
+
if (set) {
|
|
69
|
+
for (const type of listed) set.add(type)
|
|
70
|
+
}
|
|
71
|
+
return transition
|
|
72
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const HEADER_SELECTOR = "[data-pmx-sticky-header]"
|
|
2
|
+
const HIDE_THRESHOLD = 12
|
|
3
|
+
|
|
4
|
+
type ViewportChrome = {
|
|
5
|
+
header: HTMLElement
|
|
6
|
+
observer: ResizeObserver
|
|
7
|
+
height: number
|
|
8
|
+
lastScrollY: number
|
|
9
|
+
direction: -1 | 0 | 1
|
|
10
|
+
directionDistance: number
|
|
11
|
+
hidden: boolean
|
|
12
|
+
sticky: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function setInset(height: number) {
|
|
16
|
+
document.documentElement.style.setProperty(
|
|
17
|
+
"--pmx-viewport-inset-block-start",
|
|
18
|
+
`${height}px`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function setHidden(chrome: ViewportChrome, hidden: boolean) {
|
|
23
|
+
chrome.hidden = hidden
|
|
24
|
+
chrome.header.toggleAttribute("data-pmx-hidden", hidden)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function measure(chrome: ViewportChrome) {
|
|
28
|
+
chrome.sticky = getComputedStyle(chrome.header).position === "sticky"
|
|
29
|
+
chrome.height = chrome.sticky
|
|
30
|
+
? chrome.header.getBoundingClientRect().height
|
|
31
|
+
: 0
|
|
32
|
+
setInset(chrome.height)
|
|
33
|
+
if (!chrome.sticky) setHidden(chrome, false)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createChrome(header: HTMLElement): ViewportChrome {
|
|
37
|
+
const chrome: ViewportChrome = {
|
|
38
|
+
header,
|
|
39
|
+
observer: new ResizeObserver(() => measure(chrome)),
|
|
40
|
+
height: 0,
|
|
41
|
+
lastScrollY: Math.max(0, scrollY),
|
|
42
|
+
direction: 0,
|
|
43
|
+
directionDistance: 0,
|
|
44
|
+
hidden: false,
|
|
45
|
+
sticky: false,
|
|
46
|
+
}
|
|
47
|
+
chrome.observer.observe(header)
|
|
48
|
+
measure(chrome)
|
|
49
|
+
return chrome
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Coordinates opt-in sticky chrome with shared viewport CSS variables. */
|
|
53
|
+
export function installViewportChrome() {
|
|
54
|
+
let chrome: ViewportChrome | undefined
|
|
55
|
+
let listening = false
|
|
56
|
+
|
|
57
|
+
let scrollFrame = 0
|
|
58
|
+
const onScroll = () => {
|
|
59
|
+
if (!chrome || scrollFrame) return
|
|
60
|
+
scrollFrame = requestAnimationFrame(() => {
|
|
61
|
+
scrollFrame = 0
|
|
62
|
+
if (!chrome?.sticky) return
|
|
63
|
+
|
|
64
|
+
const current = Math.max(0, scrollY)
|
|
65
|
+
const delta = current - chrome.lastScrollY
|
|
66
|
+
chrome.lastScrollY = current
|
|
67
|
+
if (!delta) return
|
|
68
|
+
|
|
69
|
+
const direction = delta > 0 ? 1 : -1
|
|
70
|
+
if (direction !== chrome.direction) {
|
|
71
|
+
chrome.direction = direction
|
|
72
|
+
chrome.directionDistance = 0
|
|
73
|
+
}
|
|
74
|
+
chrome.directionDistance += Math.abs(delta)
|
|
75
|
+
|
|
76
|
+
if (current <= chrome.height || chrome.header.matches(":focus-within")) {
|
|
77
|
+
setHidden(chrome, false)
|
|
78
|
+
} else if (chrome.directionDistance >= HIDE_THRESHOLD) {
|
|
79
|
+
setHidden(chrome, direction > 0)
|
|
80
|
+
chrome.directionDistance = 0
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const listen = (enabled: boolean) => {
|
|
86
|
+
if (listening === enabled) return
|
|
87
|
+
listening = enabled
|
|
88
|
+
if (enabled) addEventListener("scroll", onScroll, { passive: true })
|
|
89
|
+
else removeEventListener("scroll", onScroll)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const reconcile = () => {
|
|
93
|
+
const header = document.querySelector<HTMLElement>(HEADER_SELECTOR)
|
|
94
|
+
if (chrome?.header === header) {
|
|
95
|
+
setHidden(chrome, chrome.hidden)
|
|
96
|
+
measure(chrome)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
chrome?.observer.disconnect()
|
|
100
|
+
chrome = header ? createChrome(header) : undefined
|
|
101
|
+
if (!chrome) setInset(0)
|
|
102
|
+
listen(chrome != null)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
addEventListener("resize", () => chrome && measure(chrome))
|
|
106
|
+
document.addEventListener("pmx:render", () =>
|
|
107
|
+
requestAnimationFrame(reconcile),
|
|
108
|
+
)
|
|
109
|
+
reconcile()
|
|
110
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Plugin } from "./types.ts"
|
|
2
|
+
|
|
3
|
+
/** Baseline application Schema. Feature Plugins refine the node types they own. */
|
|
4
|
+
export function SchemaPlugin(): Plugin {
|
|
5
|
+
return {
|
|
6
|
+
id: "schema",
|
|
7
|
+
schema: {
|
|
8
|
+
sources: {
|
|
9
|
+
page: {
|
|
10
|
+
label: "Page",
|
|
11
|
+
description: "A general rendered document.",
|
|
12
|
+
icon: "file-text",
|
|
13
|
+
color: "slate",
|
|
14
|
+
},
|
|
15
|
+
map: {
|
|
16
|
+
label: "Map",
|
|
17
|
+
description: "Source-type schema and presentation metadata.",
|
|
18
|
+
icon: "map",
|
|
19
|
+
color: "teal",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { cachedRouteTargets } from "../invalidation.ts"
|
|
2
|
+
import { parseLinks, type Source, type SourceLink } from "../source/index.ts"
|
|
3
|
+
import { directiveDefinition } from "./directive-definitions.ts"
|
|
4
|
+
import type { DependencyContext, Plugin } from "./types.ts"
|
|
5
|
+
|
|
6
|
+
const EXTERNAL = /^[a-z][a-z\d+.-]*:/i
|
|
7
|
+
type AuthoredScript = Readonly<{
|
|
8
|
+
type: "script"
|
|
9
|
+
source: string
|
|
10
|
+
module: true
|
|
11
|
+
}>
|
|
12
|
+
|
|
13
|
+
function resolveTarget(source: Source, target: string) {
|
|
14
|
+
return target.startsWith("/")
|
|
15
|
+
? target
|
|
16
|
+
: new URL(target, `file://${source.path}`).pathname
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function scriptLinks(links: readonly SourceLink[]) {
|
|
20
|
+
return links.filter((link) => link.directive?.name === "script")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveScript(
|
|
24
|
+
source: Source,
|
|
25
|
+
link: SourceLink,
|
|
26
|
+
): AuthoredScript | undefined {
|
|
27
|
+
if (link.directive?.name !== "script") return
|
|
28
|
+
const raw = link.href.split("#")[0]?.split("?")[0] ?? ""
|
|
29
|
+
if (
|
|
30
|
+
!raw ||
|
|
31
|
+
EXTERNAL.test(raw) ||
|
|
32
|
+
raw.startsWith("//") ||
|
|
33
|
+
raw.startsWith("~/")
|
|
34
|
+
)
|
|
35
|
+
return
|
|
36
|
+
const sourcePath = resolveTarget(source, raw)
|
|
37
|
+
if (!sourcePath.endsWith(".js")) return
|
|
38
|
+
return {
|
|
39
|
+
type: "script",
|
|
40
|
+
source: sourcePath,
|
|
41
|
+
module: true,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function pushUnique(assets: AuthoredScript[], asset: AuthoredScript) {
|
|
46
|
+
if (assets.some((item) => item.source === asset.source)) return
|
|
47
|
+
assets.push(asset)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function addScriptDependency(
|
|
51
|
+
dependencies: DependencyContext,
|
|
52
|
+
asset: AuthoredScript,
|
|
53
|
+
) {
|
|
54
|
+
dependencies.depend({ type: "source", path: asset.source })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sourceScriptLinks(source: Source) {
|
|
58
|
+
return source.blocks.length
|
|
59
|
+
? source.blocks.flatMap((block) => block.links)
|
|
60
|
+
: source.links
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function globalScripts(source: Source, links: readonly SourceLink[]) {
|
|
64
|
+
const assets: AuthoredScript[] = []
|
|
65
|
+
for (const link of scriptLinks(links)) {
|
|
66
|
+
if (!link.directive?.qualifiers.includes("global")) continue
|
|
67
|
+
const asset = resolveScript(source, link)
|
|
68
|
+
if (asset) pushUnique(assets, asset)
|
|
69
|
+
}
|
|
70
|
+
return assets
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function scriptSignature(assets: readonly AuthoredScript[]) {
|
|
74
|
+
return assets.map((asset) => asset.source).join("\n")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function authoredGlobalSignature(source?: Source) {
|
|
78
|
+
return scriptSignature(
|
|
79
|
+
source ? globalScripts(source, parseLinks(source.body)) : [],
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function ScriptsPlugin(): Plugin {
|
|
84
|
+
const global = new Map<string, AuthoredScript[]>()
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
id: "scripts",
|
|
88
|
+
|
|
89
|
+
index(source) {
|
|
90
|
+
const assets = globalScripts(source, sourceScriptLinks(source))
|
|
91
|
+
if (assets.length) global.set(source.path, assets)
|
|
92
|
+
else global.delete(source.path)
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
unindex(sourcePath) {
|
|
96
|
+
global.delete(sourcePath)
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
precompile(markdown, ctx) {
|
|
100
|
+
const scripts = scriptLinks(ctx.block?.links ?? ctx.source.links)
|
|
101
|
+
if (!scripts.length) return markdown
|
|
102
|
+
let current = markdown
|
|
103
|
+
for (const link of scripts) {
|
|
104
|
+
current = current.replace(directiveDefinition("script", link.href), "")
|
|
105
|
+
}
|
|
106
|
+
return current
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
invalidate({ before, after }, app) {
|
|
110
|
+
if (authoredGlobalSignature(before) === authoredGlobalSignature(after))
|
|
111
|
+
return
|
|
112
|
+
const pages = app.sources.builtPages()
|
|
113
|
+
return {
|
|
114
|
+
sourceIds: pages.map((page) => page.sourceId),
|
|
115
|
+
targets: cachedRouteTargets(pages),
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
page(html, ctx) {
|
|
120
|
+
for (const assets of global.values()) {
|
|
121
|
+
for (const asset of assets) addScriptDependency(ctx, asset)
|
|
122
|
+
}
|
|
123
|
+
for (const link of scriptLinks(ctx.source.links)) {
|
|
124
|
+
if (link.directive?.qualifiers.includes("global")) continue
|
|
125
|
+
const asset = resolveScript(ctx.source, link)
|
|
126
|
+
if (asset) {
|
|
127
|
+
ctx.addAsset(asset)
|
|
128
|
+
addScriptDependency(ctx, asset)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return html
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
documentAssets(ctx) {
|
|
135
|
+
for (const origin of [...global.keys()].sort()) {
|
|
136
|
+
for (const asset of global.get(origin) ?? []) ctx.addAsset(asset)
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
}
|