From 99478e1a05ca84ce4df86aecffd820fedf269a4d Mon Sep 17 00:00:00 2001 From: Oscar Levin Date: Thu, 2 Apr 2026 08:17:55 -0600 Subject: [PATCH 01/12] Add JS modernization plan and Copilot instructions - script/jsbuilder/PLAN.md: full plan for converting JS to ES modules, removing jQuery, and building bundles with esbuild (mirroring cssbuilder) - .github/copilot-instructions.md: Copilot context for this repo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 88 +++++++++++++++++++++++ script/jsbuilder/PLAN.md | 123 ++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 script/jsbuilder/PLAN.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..2ee9f5856 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,88 @@ +# Copilot Instructions for PreTeXt + +## What is PreTeXt + +PreTeXt is an XML-based authoring and publishing system for STEM textbooks and academic documents. A single XML source file is transformed into multiple output formats (HTML5, LaTeX/PDF, EPUB, Braille, Jupyter notebooks, reveal.js, etc.) via XSLT stylesheets. The Python CLI orchestrates the conversion pipeline, calling external tools (LaTeX, Asymptote, SageMath, Playwright, etc.) as needed. + +## Repository Layout + +| Directory | Role | +|-----------|------| +| `xsl/` | XSLT 1.0 stylesheets — the core conversion engine (~72K lines) | +| `pretext/` | Python CLI (`pretext/pretext`) and library (`pretext/lib/pretext.py`) | +| `css/` | SCSS/CSS theming system; `script/cssbuilder/` compiles it | +| `js/` | Client-side JavaScript for interactive HTML output | +| `schema/` | RELAX-NG schema; `pretext.xml` is the **only** file to edit directly | +| `examples/` | Sample PreTeXt documents (`minimal/`, `sample-article/`) | +| `doc/` | Author and Publisher guides | + +## Build & Test Commands + +### XSLT tests +```bash +cd xsl/tests +xsltproc pretext-text-utilities-test.xsl null.xml +# Success prints "Tests complete!"; failures print error details +``` + +### CSS build +```bash +cd script/cssbuilder +npm install # one-time setup +npm run build # compiles SCSS → css/dist/ +``` + +### Python CLI (development use) +```bash +# Run conversions directly via the CLI entry point: +python3 pretext/pretext -c -f -s -p -d +# Example: build HTML +python3 pretext/pretext -f html -s source/main.ptx -p publication.xml -d output/ +``` + +There is no automated test suite or CI pipeline. For Python, `pretext/module-test.py` is an informal integration example, not a test runner. + +## Architecture: How a Conversion Works + +1. **Assembly pass** — `xsl/pretext-assembly.xsl` pre-processes source XML into an "enhanced" document (resolves `xi:include`, computes IDs, etc.). +2. **Format pass** — a format-specific stylesheet (e.g., `xsl/pretext-html.xsl`) imports the assembly and common base, then templates match elements to produce output. +3. **Extraction passes** — `xsl/extract-*.xsl` stylesheets isolate embeddable content (LaTeX images, Asymptote diagrams, WeBWorK problems) for separate processing by external tools. +4. **Python orchestration** — `pretext/lib/pretext.py` calls `xsltproc` and external tools in the correct sequence, manages temp directories, and assembles the final output. + +### XSL import hierarchy (example for HTML) +``` +pretext-html.xsl + ├── pretext-assembly.xsl + ├── pretext-common.xsl ← shared base templates + ├── pretext-runestone.xsl + └── html-symbols.xsl (include, not import) +``` +Format-specific sheets override base templates via XSLT import precedence. + +## Key Conventions + +### XSLT +- All stylesheets use **XSLT 1.0** with EXSL extensions (`exsl`, `str`, `dyn`). +- Internal namespace: `xmlns:pi="http://pretextbook.org/2020/pretext/internal"`. +- Shared entity references live in `entities.ent` and are included via DOCTYPE. +- Template names use `kebab-case`; modes are also `kebab-case` strings. +- `pretext-common.xsl` is the base library; format sheets extend it via ``. +- `extract-*.xsl` files follow the convention of isolating one content type per file. + +### Python +- **Never** use `from foo import *` or `from foo import bar`. Always `import foo` or `import foo as ALIAS` (stated explicitly in the module header). +- Infrequently-used libraries are imported **inside functions**, not at module level. +- All logging goes through `log = logging.getLogger('ptxlogger')`. +- Function names follow `verb_noun()` style; private helpers use `_verb_noun()`. +- The single library module is `pretext/lib/pretext.py` (~6300 lines); there is no split into sub-modules. + +### Schema +- **Edit only `schema/pretext.xml`** (literate RELAX-NG compact notation). The `.rnc`, `.rng`, and `.xsd` files are generated via `schema/build.sh` using the `trang` tool. +- Schematron rules for author-facing validation are in `schema/pretext.sch` / `schema/pretext-schematron.xsl`. + +## Development Workflow (from CONTRIBUTING.md) + +- Discuss changes on the [pretext-dev Google Group](https://groups.google.com/forum/#!forum/pretext-dev) **before** writing code. +- Work on a topic branch; **rebase onto `master`** (never merge `master` into your branch). +- Keep commits minimal — maintainers reorganize history during integration. +- Submit via pull request; do not add commits or rebase after submission unless asked. diff --git a/script/jsbuilder/PLAN.md b/script/jsbuilder/PLAN.md new file mode 100644 index 000000000..9d6606da0 --- /dev/null +++ b/script/jsbuilder/PLAN.md @@ -0,0 +1,123 @@ +# JS Modernization Plan + +Modernize how PreTeXt provides JavaScript to the HTML it builds — mirroring +the existing CSS build system (`script/cssbuilder/` → `css/dist/`). + +## Goals + +- Convert JS source files to ES modules (import/export) +- Remove jQuery dependency (replace with vanilla DOM APIs) +- Bundle with esbuild into `js/dist/` (pre-built files committed to repo) +- Update XSL and Python to reference the new bundles +- Ship pre-built dist so users without Node can still build + +## Bundle Targets + +| Bundle | Source files | When loaded | Output | +|--------|-------------|-------------|--------| +| `pretext-core` | `pretext.js`, `pretext_add_on.js`, `knowl.js`, `lti_iframe_resizer.js` | Always | `js/dist/pretext-core.js` | +| `pretext-search` | `pretext_search.js`, `ptx_search.js` | `$has-native-search` only | `js/dist/pretext-search.js` | + +## Out of Scope + +These are left untouched: + +- `js/pretext-webwork/` — versioned, complex server integration +- `js/pretext-stack/` — specialized server integration +- `js/diagcess/` — already an npm-bundled package +- `js/mathjaxknowl3.js` — loaded dynamically by MathJax via + `"paths": {"pretext": "_static/pretext/js"}`; must stay a standalone file + +## Final Directory Structure + +``` +js/ +├── pretext.js ← ES module source +├── pretext_add_on.js ← ES module source +├── knowl.js ← ES module source +├── lti_iframe_resizer.js ← ES module source +├── pretext_search.js ← ES module source +├── ptx_search.js ← ES module source +├── mathjaxknowl3.js ← unchanged (MathJax extension) +├── pretext-webwork/ ← unchanged +├── pretext-stack/ ← unchanged +├── diagcess/ ← unchanged +└── dist/ + ├── pretext-core.js ← built bundle (committed) + ├── pretext-core.js.map + ├── pretext-search.js ← built bundle (committed) + └── pretext-search.js.map + +script/jsbuilder/ ← new (this directory) +├── package.json +├── jsbuilder.mjs +├── README.md +└── PLAN.md ← this file +``` + +`js/jquery.min.js` is deleted once jQuery is removed from all source files. + +## jQuery → Vanilla DOM Cheatsheet + +| jQuery | Vanilla | +|--------|---------| +| `$(fn)` / `$(document).ready(fn)` | `document.addEventListener('DOMContentLoaded', fn)` | +| `$('.sel')` | `document.querySelectorAll('.sel')` | +| `$(el)` wrapping | work directly on `el` | +| `$el.addClass/removeClass/toggleClass` | `el.classList.add/remove/toggle` | +| `$el.on/off` | `el.addEventListener/removeEventListener` | +| `$el.attr(k, v)` / `$el.prop` | `el.setAttribute(k, v)` / `el.getAttribute(k)` | +| `$el.find('.sel')` | `el.querySelectorAll('.sel')` | +| `$el.closest('.sel')` | `el.closest('.sel')` (native) | +| `$el.html(s)` | `el.innerHTML = s` | +| `$el.text(s)` | `el.textContent = s` | +| `$el.show()` / `$el.hide()` | `el.style.display = ''` / `'none'` | +| `$el.slideUp()` / `slideDown()` | CSS `max-height` transition or Web Animations API | +| `$el.append(child)` | `el.append(child)` (native) | +| `$el.prepend(child)` | `el.prepend(child)` (native) | +| `$el.remove()` | `el.remove()` (native) | +| `$.ajax(...)` | `fetch(...)` | +| `$el.trigger('click')` | `el.dispatchEvent(new Event('click'))` | +| `$el.data(k, v)` | `el.dataset.k = v` | + +## Tasks (in order) + +### Phase 1 — Build infrastructure +- [ ] Create `script/jsbuilder/package.json` (esbuild dep, build/watch scripts) +- [ ] Create `script/jsbuilder/jsbuilder.mjs` (two entry points, output to `js/dist/`) +- [ ] Create `script/jsbuilder/README.md` + +### Phase 2 — ES module conversion + jQuery removal +Each file: add `import`/`export`, remove jQuery, replace with vanilla DOM. + +- [ ] `js/pretext.js` (261 lines — TOC, sidebar, hash nav) +- [ ] `js/knowl.js` (338 lines — slide animations → CSS transitions) +- [ ] `js/lti_iframe_resizer.js` (54 lines — likely minimal jQuery) +- [ ] `js/pretext_search.js` (315 lines) + `js/ptx_search.js` (112 lines) +- [ ] `js/pretext_add_on.js` (1,339 lines — largest; image magnify, permalink, GeoGebra) + +### Phase 3 — Bundle entry points +- [ ] `js/pretext-core-entry.js` — imports + inits core modules +- [ ] `js/pretext-search-entry.js` — imports + inits search modules + +### Phase 4 — Build and commit dist +- [ ] Run `npm run build` in `script/jsbuilder/` +- [ ] Commit `js/dist/` pre-built files +- [ ] Delete `js/jquery.min.js` + +### Phase 5 — XSL update (`xsl/pretext-html.xsl`) +- [ ] `pretext-js` template: replace 3 ` @@ -12470,7 +12470,7 @@ along with MathBook XML. If not, see . true); - + @@ -13755,7 +13755,7 @@ TODO: - - knowl.js code controls Sage Cells within knowls - - + From 0aae07a8a35bcd0482b5db42313c71e90d33e331 Mon Sep 17 00:00:00 2001 From: Oscar Levin Date: Thu, 2 Apr 2026 11:19:19 -0600 Subject: [PATCH 08/12] Exclude js/src/ from deployed JS copy (Phase 6) Update copy_html_js() to exclude the js/src/ directory when copying JS files to the build output. Only the pre-built bundles in js/dist/ are needed at runtime; the ES module source files are development-only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pretext/lib/pretext.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pretext/lib/pretext.py b/pretext/lib/pretext.py index efe28259a..01bddead0 100644 --- a/pretext/lib/pretext.py +++ b/pretext/lib/pretext.py @@ -6019,13 +6019,15 @@ def copy_managed_directories(build_dir, external_abs=None, generated_abs=None, d def copy_html_js(work_dir): - '''Copy all necessary CSS and JS into working directory''' + '''Copy all necessary JS into working directory''' # Place support files where expected. # We are not careful about placing only modules that are needed, all are copied. + # Exclude js/src/ (ES module sources) since only the built bundles in js/dist/ + # are needed at runtime. js_src = os.path.join(get_ptx_path(), "js") js_dest = os.path.join(work_dir, "_static", "pretext", "js") - shutil.copytree(js_src, js_dest) + shutil.copytree(js_src, js_dest, ignore=shutil.ignore_patterns("src")) def copy_build_directory(build_dir, dest_dir): From 37f3548bdfe5862023f4f7353127390fe0f1210a Mon Sep 17 00:00:00 2001 From: Oscar Levin Date: Thu, 2 Apr 2026 11:20:04 -0600 Subject: [PATCH 09/12] Mark all plan phases complete Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- script/jsbuilder/PLAN.md | 88 ++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/script/jsbuilder/PLAN.md b/script/jsbuilder/PLAN.md index 02ffec00b..19682efa0 100644 --- a/script/jsbuilder/PLAN.md +++ b/script/jsbuilder/PLAN.md @@ -135,55 +135,55 @@ script/jsbuilder/ ← new (this directory) ## Tasks (in order) -### Phase 1 — Build infrastructure -- [ ] Create `script/jsbuilder/package.json` (esbuild dep, build/watch scripts) -- [ ] Create `script/jsbuilder/jsbuilder.mjs` (entry points, output to `js/dist/`) -- [ ] Create `script/jsbuilder/README.md` +### Phase 1 — Build infrastructure ✅ +- [x] Create `script/jsbuilder/package.json` (esbuild dep, build/watch scripts) +- [x] Create `script/jsbuilder/jsbuilder.mjs` (entry points, output to `js/dist/`) +- [x] Create `script/jsbuilder/README.md` -### Phase 2 — Modularize `pretext_add_on.js` +### Phase 2 — Modularize `pretext_add_on.js` ✅ Break the monolith into `js/src/` modules. Remove jQuery (~35 calls, concentrated in image-magnify, auto-id, video-magnify, GeoGebra, and keyboard-nav sections). Fix known bugs (ENTER/ESC fallthrough, broken `knowl_focus_stack` reference). -- [ ] Create `js/src/permalink.js` -- [ ] Create `js/src/image-magnify.js` (remove jQuery) -- [ ] Create `js/src/geogebra.js` (remove jQuery — heaviest: ~9 calls) -- [ ] Create `js/src/keyboard-nav.js` (remove jQuery, fix ESC/ENTER bug, fix broken knowl_focus_stack ref) -- [ ] Create `js/src/print-preview/` (9 sub-modules — already jQuery-free) -- [ ] Create `js/src/theme.js` -- [ ] Create `js/src/embed.js` -- [ ] Create `js/src/code-copy.js` -- [ ] Create `js/src/deprecated/video-magnify.js` (flagged dead) -- [ ] Create `js/src/deprecated/auto-id.js` (flagged suspicious, remove jQuery) -- [ ] Create `js/src/deprecated/scrollbar-width.js` - -### Phase 3 — Convert existing standalone files to ES modules -These are already jQuery-free; just add `import`/`export` and clean up globals. - -- [ ] `js/pretext.js` (227 lines — TOC, sidebar, hash nav) -- [ ] `js/knowl.js` (286 lines — knowl open/close) -- [ ] `js/lti_iframe_resizer.js` (50 lines — LTI message handler) -- [ ] `js/pretext_search.js` (296 lines — search UI) - -### Phase 4 — Bundle entry points + build -- [ ] Create `js/src/pretext-core-entry.js` — imports + inits all core modules -- [ ] Create `js/src/pretext-search-entry.js` — imports + inits search -- [ ] Run `npm run build` in `script/jsbuilder/` -- [ ] Commit `js/dist/` pre-built files -- [ ] Delete `js/jquery.min.js` - -### Phase 5 — XSL update (`xsl/pretext-html.xsl`) -- [ ] `pretext-js` template (line ~13869): replace 3 ` +