Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Includes a custom layout editor that lets you drag/resize slide elements (red ba
- `setup/preparser.ts` — registers a `transformSlide` preparser extension that injects carried title/subtitle headings into a slide's content and syncs the result into Slidev's own parsed `slide.title`
- `_override/SideEditor.vue` — custom Slidev SideEditor override with a "Layout" tab
- `assets/` — the CodeURJC and URJC brand images used by the layouts, shipped inside the package and imported as ES module assets (`import logoUrl from '../assets/logo.png'`) rather than read from the consumer's `public/images/`. A consumer that never creates a `public/` at all still renders the branded layouts; the old `<img src="/images/...">` form silently resolved to the SPA's index.html in dev (a broken image) and failed `slidev build` outright with `UNRESOLVED_IMPORT` — the theme's brand assets are package-owned, so they can't be left to consumer content. `public/images/` remains consumer-owned, as the destination for pasted images.
- `vite.config.ts` — Vite transform hook that injects the SideEditor override; `/api/save-layout` middleware that persists layout CSS variables; `/api/save-code-highlight-position` middleware that persists a dragged callout's position into the markdown file the dragged callout's own slide came from, as reported by the client (`$route.meta.slide.filepath`) and re-validated here as a markdown file within the project root. It must not assume `slides.md`: the deck entry can be named anything (a course is typically one deck per lecture — `tema1.md`, `tema2.md`, ...) and one deck can pull further slides in from other files via `src:`, so an assumed filename splices the `@x,y` into an unrelated deck rather than failing. Consumer-owned paths (`layouts/` write target, the deck markdown) resolve against Vite's `server.config.root` (the consuming project's root), never against `import.meta.dirname` (this plugin file's own location inside `node_modules`) — package-owned paths (`_override/SideEditor.vue`, `assets/`, the fallback `layouts/default.vue` template) stay `import.meta.dirname`-relative. Two further plugins there are likewise deck-filename-agnostic: the post-`listening` reparse nudge (which forces preparser extensions like title carry-over to apply on a cold start) emits a synthetic change for every top-level `.md` in the project root, since Slidev's resolved `options.entry` is never exposed to a theme's own Vite config and its `handleHotUpdate` no-ops on files outside the running deck's `data.watchFiles`; and the slide-module force-invalidation matches any `.md`, not just `slides.md`. The save-layout middleware falls back to reading the theme's own bundled layout when a consumer has no local override yet, and always writes back into the consumer's `layouts/`, creating a consumer-local override from the first edit onward. Falling back that way rewrites the template's package-relative imports (`../composables/...`, `../assets/...`) to bare `codeurjc-slidev-theme/...` specifiers, which still resolve once the file sits in the consumer's own `layouts/` dir.
- `vite.config.ts` — Vite transform hook that injects the SideEditor override; `/api/save-layout` middleware that persists layout CSS variables; `/api/save-code-highlight-position` middleware that persists a dragged callout's position into the markdown file the dragged callout's own slide came from, as reported by the client (`$route.meta.slide.filepath`) and re-validated here as a markdown file within the project root. It must not assume `slides.md`: the deck entry can be named anything (a course is typically one deck per lecture — `tema1.md`, `tema2.md`, ...) and one deck can pull further slides in from other files via `src:`, so an assumed filename splices the `@x,y` into an unrelated deck rather than failing. Consumer-owned paths (`layouts/` write target, the deck markdown) resolve against Vite's `server.config.root` (the consuming project's root), never against `import.meta.dirname` (this plugin file's own location inside `node_modules`) — package-owned paths (`_override/SideEditor.vue`, `assets/`, the fallback `layouts/default.vue` template) stay `import.meta.dirname`-relative. The slide-module force-invalidation plugin there is likewise deck-filename-agnostic: it matches any `.md`, not just `slides.md`. No cold-start reparse nudge is needed for preparser extensions like title carry-over: since Slidev 52.17.1, `resolveOptions` reloads the deck with the theme's roots before serving or building, so `setup/preparser.ts` applies from the first load (in dev and in `slidev build`/`export`) — hence the theme's `^52.17.1` peer floor. The save-layout middleware falls back to reading the theme's own bundled layout when a consumer has no local override yet, and always writes back into the consumer's `layouts/`, creating a consumer-local override from the first edit onward. Falling back that way rewrites the template's package-relative imports (`../composables/...`, `../assets/...`) to bare `codeurjc-slidev-theme/...` specifiers, which still resolve once the file sits in the consumer's own `layouts/` dir.

Slidev auto-loads a theme package's `vite.config.ts`, `layouts/`, `setup/`, and `components/` by globbing every root in `[...theme/addon roots, userRoot]` — a consumer needs zero local Vite config for any of this to work.

Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@
"prepare": "simple-git-hooks"
},
"dependencies": {
"@slidev/cli": "^52.15.0",
"@slidev/client": "52.16.0",
"@slidev/cli": "^52.19.1",
"@slidev/client": "^52.19.1",
"codeurjc-slidev-theme": "workspace:*"
},
"devDependencies": {
"@antfu/eslint-config": "^9.2.0",
"@playwright/test": "^1.61.1",
"eslint": "^10.8.0",
"lint-staged": "^17.2.0",
"simple-git-hooks": "^2.13.1",
"vite": "^8.1.0"
"@antfu/eslint-config": "^9.5.1",
"@playwright/test": "^1.63.0",
"eslint": "^10.10.0",
"lint-staged": "^17.5.1",
"simple-git-hooks": "^2.14.0",
"vite": "^8.3.0"
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
Expand Down
14 changes: 7 additions & 7 deletions packages/codeurjc-slidev-theme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,27 @@
"typecheck": "vue-tsc --noEmit"
},
"peerDependencies": {
"@slidev/cli": "^52.15.0",
"@slidev/client": "^52.15.0",
"@slidev/cli": "^52.17.1",
"@slidev/client": "^52.17.1",
"vue": "^3.5.39"
},
"dependencies": {
"@vueuse/core": "^14.3.0",
"markdown-it": "^14.2.0",
"unocss": "^66.7.3"
"unocss": "^66.10.2"
},
"devDependencies": {
"@slidev/types": "^52.18.0",
"@slidev/types": "^52.19.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/vue": "^8.1.0",
"@types/file-saver": "^2.0.7",
"@types/markdown-it": "^14.1.2",
"@types/markdown-it": "^14.2.0",
"@types/node": "^22.20.1",
"@vitejs/plugin-vue": "^6.0.7",
"jsdom": "^29.1.1",
"typescript": "^5.9.3",
"vitest": "^4.1.9",
"vue": "^3.5.39",
"vue-tsc": "^3.3.9"
"vue": "^3.5.42",
"vue-tsc": "^3.3.11"
}
}
56 changes: 0 additions & 56 deletions packages/codeurjc-slidev-theme/setup/transformers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { SlideForHeadingResolve } from '../composables/useSlideTitleCarryover'
import type { CombinedSourceLink } from '../composables/useSnippetImport'
import { readFileSync } from 'node:fs'
import { basename, dirname, resolve } from 'node:path'
Expand All @@ -9,7 +8,6 @@ import {
parseCodeHighlights,
parseExternalHighlightAnchors,
} from '../composables/useCodeHighlights'
import { injectCarriedHeadings, isDefaultLayout, parseLeadingHeadings, resolveSlideHeadings } from '../composables/useSlideTitleCarryover'
import {
combineCodeAndAnchors,

Expand Down Expand Up @@ -68,60 +66,6 @@ function wrapCodeBlock(info: string, html: string, sourceLink: CombinedSourceLin

export default defineTransformersSetup(() => ({
pre: [
// Belt-and-suspenders alongside setup/preparser.ts's transformSlide hook:
// Slidev resolves the active theme *from* slides.md's own headmatter, so
// its very first parse of the file (just to read that headmatter) runs
// before the theme -- and thus this package's setup/preparser.ts -- is
// even known, using a roots list that excludes it. That first parse's
// result becomes `ctx.options.data` for the rest of the process's
// lifetime; in `slidev build`/`slidev export` there is no later
// reparse-with-full-roots to correct it (unlike the dev server, which
// gets one via a real file edit), so the preparser's injection silently
// never applies there and carried titles are simply absent from the
// exported output. This transformer recomputes the same carry-over
// decision independently, from `ctx.options.data.slides` (always present
// and already reflecting every slide's own raw content/frontmatter,
// regardless of whether the preparser ran) -- so rendering is correct
// even when the preparser's `slide.title`/TOC feedback isn't. Runs first
// so its overwrite (confined to the leading heading lines only, via the
// common-suffix diff below) can't collide with the snippet-import
// transformer below, which only ever touches later lines.
(ctx) => {
if (!isDefaultLayout(ctx.slide.frontmatter))
return
const content = ctx.s.original
const own = parseLeadingHeadings(content)
const allSlides: SlideForHeadingResolve[] = ctx.options.data.slides.map(s => ({
content: s.content,
frontmatter: s.frontmatter,
}))
const resolved = resolveSlideHeadings(allSlides, ctx.slide.index)
const newContent = injectCarriedHeadings(content, own, resolved)
if (newContent === content)
return

// Diffed down to a common-suffix overwrite (rather than replacing the
// whole slide) so this can never collide with the snippet-import
// transformer below, which only ever touches lines further down.
let suffixLen = 0
const maxSuffix = Math.min(content.length, newContent.length)
while (
suffixLen < maxSuffix
&& content[content.length - 1 - suffixLen] === newContent[newContent.length - 1 - suffixLen]
) {
suffixLen++
}

const overwriteEnd = content.length - suffixLen
const newPrefix = newContent.slice(0, newContent.length - suffixLen)
// A slide with no leading blank line and no own heading at all (carried
// title/subtitle purely prepended, nothing of the original consumed)
// diffs down to a zero-length range, which MagicString's `overwrite`
// rejects -- insert instead.
if (overwriteEnd === 0)
ctx.s.appendLeft(0, newPrefix)
else ctx.s.overwrite(0, overwriteEnd, newPrefix)
},
// Rewrites `<<< @/path[selector] lang` lines into a literal fenced code
// block *before* markdown-it (and therefore Slidev's own native `<<<`
// rule, which only slices via in-file #region markers) ever parses the
Expand Down
42 changes: 2 additions & 40 deletions packages/codeurjc-slidev-theme/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Buffer } from 'node:buffer'
import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { existsSync, readFileSync } from 'node:fs'
import { relative, resolve } from 'node:path'
import process from 'node:process'
import { defineConfig } from 'vite'
import { serializeMarkerOverride } from './composables/useCodeHighlights'
import { serializeMarkerOverride } from './composables/useCodeHighlights.ts'

const VAR_MAP: Record<string, Record<string, string>> = {
'red-bar': { y: '--ed-red-y', x: '--ed-red-x', w: '--ed-red-w', h: '--ed-red-h' },
Expand Down Expand Up @@ -377,44 +377,6 @@ export default defineConfig({
})
},
},
{
// Slidev resolves the active theme from `slides.md`'s own headmatter,
// which means its *very first* parse of the file (used just to read
// that headmatter, before the theme -- and thus this package's own
// `setup/preparser.ts` -- is even known) runs with a roots list that
// doesn't include this theme yet. That first parse's result becomes
// the server's permanent in-memory slide data unless/until a real file
// edit triggers a reparse (which does use the fully-resolved roots) --
// so on a cold `pnpm dev` start, preparser-driven features like slide
// title/subtitle carry-over silently never apply to any slide, until
// the presenter happens to edit the deck. Forcing one synthetic
// change event right after the dev server starts listening triggers
// that same reparse path immediately, so carry-over (and any other
// preparser extension) is correct from the first load rather than
// only after a first edit.
//
// Which file is the deck isn't knowable from here -- Slidev's resolved
// `options.entry` is never exposed to a theme's own Vite config, and
// the entry is only `slides.md` by convention (`slidev tema2.md` is
// just as valid). Nudging every top-level markdown file in the project
// root covers whichever one it is: Slidev's own `handleHotUpdate`
// looks the changed file up in its `data.watchFiles` map and returns
// immediately for anything that isn't part of the running deck, so the
// extra events for e.g. a README are no-ops. The entry is always
// directly in this root, since Slidev derives `userRoot` (Vite's
// `config.root`) as the entry's own directory.
name: 'slidev-force-initial-reparse-for-preparser-extensions',
configureServer(server) {
server.httpServer?.once('listening', () => {
const candidates = readdirSync(server.config.root, { withFileTypes: true })
.filter(e => e.isFile() && e.name.endsWith('.md'))
.map(e => resolve(server.config.root, e.name))
setTimeout(() => {
for (const entry of candidates) server.watcher.emit('change', entry)
}, 0)
})
},
},
{
// Slidev's own `handleHotUpdate` only re-transforms/pushes an update
// for a slide's virtual `__slidev_<n>.md`/`.frontmatter` module when
Expand Down
4 changes: 2 additions & 2 deletions packages/create-codeurjc-slidev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
"node": ">=20.12.0"
},
"dependencies": {
"ansis": "^4.3.1",
"ansis": "^4.4.0",
"minimist": "^1.2.8",
"pathe": "^2.0.3",
"prompts": "^2.4.2",
"tinyexec": "^1.2.4"
"tinyexec": "^1.3.1"
}
}
2 changes: 1 addition & 1 deletion packages/create-codeurjc-slidev/template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"export": "slidev export"
},
"dependencies": {
"@slidev/cli": "^52.15.0",
"@slidev/cli": "^52.19.1",
"codeurjc-slidev-theme": "^0.1.1",
"vue": "^3.5.39"
}
Expand Down
Loading
Loading