diff --git a/.gitignore b/.gitignore index 93da4f6..fa4165b 100644 --- a/.gitignore +++ b/.gitignore @@ -314,4 +314,5 @@ pyrightconfig.json .vscode output -tmp \ No newline at end of file +tmp +scraper/src/libretexts2zim/mathjax \ No newline at end of file diff --git a/scraper/openzim.toml b/scraper/openzim.toml new file mode 100644 index 0000000..95887c8 --- /dev/null +++ b/scraper/openzim.toml @@ -0,0 +1,9 @@ +[files.assets.config] +target_dir="src/libretexts2zim" + +[files.assets.actions."mathjax"] +action="extract_items" +source="https://github.com/mathjax/MathJax/archive/refs/tags/3.2.2.zip" +zip_paths=["MathJax-3.2.2"] +target_paths=["mathjax"] +remove = ["mathjax/.github","mathjax/.gitignore","mathjax/.travis.yml","mathjax/bower.json","mathjax/composer.json","mathjax/CONTRIBUTING.md","mathjax/package.json"] \ No newline at end of file diff --git a/scraper/pyproject.toml b/scraper/pyproject.toml index 97b2ec8..0322b7d 100644 --- a/scraper/pyproject.toml +++ b/scraper/pyproject.toml @@ -31,6 +31,8 @@ allow-direct-references = true kind = "scraper" additional-keywords = ["libretexts"] +[tool.hatch.build.hooks.openzim-build] + [project.optional-dependencies] scripts = ["invoke==2.2.0"] lint = ["black==24.8.0", "ruff==0.6.4"] diff --git a/scraper/src/libretexts2zim/client.py b/scraper/src/libretexts2zim/client.py index de555d8..9857fd3 100644 --- a/scraper/src/libretexts2zim/client.py +++ b/scraper/src/libretexts2zim/client.py @@ -295,8 +295,7 @@ def _process_tree_data(page_node: Any, parent: LibraryPage) -> None: return tree_obj def get_page_content(self, page: LibraryPage) -> LibraryPageContent: - """Returns the content of a given page""" - + """Returns the 'raw' content of a given page""" tree = self._get_api_json( f"/pages/{page.id}/contents", timeout=HTTP_TIMEOUT_NORMAL_SECONDS ) diff --git a/scraper/src/libretexts2zim/processor.py b/scraper/src/libretexts2zim/processor.py index 2a77519..c794e24 100644 --- a/scraper/src/libretexts2zim/processor.py +++ b/scraper/src/libretexts2zim/processor.py @@ -259,7 +259,45 @@ def run(self) -> Path: ).model_dump_json(by_alias=True), ) - logger.info(" Storing the ZIM UI") + logger.info(f"Adding Vue.JS UI files in {self.zimui_dist}") + for file in self.zimui_dist.rglob("*"): + if file.is_dir(): + continue + path = str(Path(file).relative_to(self.zimui_dist)) + logger.debug(f"Adding {path} to ZIM") + if path == "index.html": # Change index.html title and add to ZIM + index_html_path = self.zimui_dist / path + add_item_for( + creator=creator, + path=path, + content=index_html_path.read_text(encoding="utf-8").replace( + "Vite App", + f"{formatted_config.title_format}", + ), + mimetype="text/html", + is_front=True, + ) + else: + add_item_for( + creator=creator, + path=path, + fpath=file, + is_front=False, + ) + + mathjax = (Path(__file__) / "../mathjax").resolve() + logger.info(f"Adding mathjax files in {mathjax}") + for file in mathjax.rglob("*"): + if not file.is_file(): + continue + path = str(Path(file).relative_to(mathjax.parent)) + logger.debug(f"Adding {path} to ZIM") + add_item_for( + creator=creator, + path=path, + fpath=file, + is_front=False, + ) logger.info(" Fetching and storing home page...") home = self.libretexts_client.get_home() @@ -308,32 +346,6 @@ def run(self) -> Path: # missing logger.debug(f"Ignoring {asset_path} due to {exc}") - logger.info(f"Adding Vue.JS UI files in {self.zimui_dist}") - for file in self.zimui_dist.rglob("*"): - if file.is_dir(): - continue - path = str(Path(file).relative_to(self.zimui_dist)) - logger.debug(f"Adding {path} to ZIM") - if path == "index.html": # Change index.html title and add to ZIM - index_html_path = self.zimui_dist / path - add_item_for( - creator=creator, - path=path, - content=index_html_path.read_text(encoding="utf-8").replace( - "Vite App", - f"{formatted_config.title_format}", - ), - mimetype="text/html", - is_front=True, - ) - else: - add_item_for( - creator=creator, - path=path, - fpath=file, - is_front=False, - ) - logger.info("Fetching pages tree") pages_tree = self.libretexts_client.get_page_tree() selected_pages = self.content_filter.filter(pages_tree) diff --git a/zimui/public/.gitignore b/zimui/public/.gitignore index 6b584e8..ecf4dd6 100644 --- a/zimui/public/.gitignore +++ b/zimui/public/.gitignore @@ -1 +1,2 @@ -content \ No newline at end of file +content +mathjax \ No newline at end of file diff --git a/zimui/src/directives/mathjax.ts b/zimui/src/directives/mathjax.ts new file mode 100644 index 0000000..8727940 --- /dev/null +++ b/zimui/src/directives/mathjax.ts @@ -0,0 +1,100 @@ +/* +Directive to handle DOM manipulation to add/remove MathJax everytime the page change + +This is a bit hackhish but the only reliable solution found so far, and probably the +most robust one. + +The dynamic behavior of removing / adding back MathJax is wanted/necessary because we +need to dynamically set the PageIndex macro to dynamically display proper figures +/ equations / ... numbering. + +This directive MUST be used only once in the whole Vue.JS application. + +MathJax settings are an adaptation of libretexts.org settings, for MathJax 3 (including +extensions now removed or not yet supported or included by default). +*/ +import type { DirectiveBinding } from 'vue' + +let front: string | undefined = undefined + +const frontFromTitle = function (title: string): string { + // Computes front value from page title. + // E.g. if page title is `1.2: The Scientific Method` then front is `1.2.` + let front: string = '' + if (title.includes(':')) { + front = title.split(':')[0] + if (front.includes('.')) { + const parts: string[] = front.split('.') + front = parts.map((int) => (int.includes('0') ? parseInt(int, 10) : int)).join('.') + } + front += '.' + } + return front +} + +const removeMathJax = function () { + const script = document.getElementById('mathjax-script') + if (script) script.remove() + if (window.MathJax) delete window.MathJax +} + +const addMathJax = function (front: string) { + window.MathJax = { + section: front, + tex: { + tags: 'all', + macros: { + PageIndex: ['{' + front + '#1}'.toString(), 1] + }, + autoload: { + color: [], + colorv2: ['color'] + }, + packages: { '[+]': ['noerrors', 'mhchem', 'tagFormat', 'color', 'cancel'] } + }, + loader: { + load: ['[tex]/noerrors', '[tex]/mhchem', '[tex]/tagFormat', '[tex]/colorv2', '[tex]/cancel'] + }, + svg: { + scale: 0.85 + }, + options: { + menuOptions: { + settings: { + zoom: 'Double-Click', + zscale: '150%' + } + } + } + } + const script = document.createElement('script', { + id: 'mathjax-script' + } as ElementCreationOptions) + script.src = './mathjax/es5/tex-svg.js' + script.async = false + document.head.appendChild(script) +} + +const vMathJax = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + front = frontFromTitle(binding.value.title) + removeMathJax() // "just-in-case" + addMathJax(front) + }, + updated(el: HTMLElement, binding: DirectiveBinding) { + // Reload MathJax only if title has changed (allow to debounce update which might be + // called multiple times) + const new_front = frontFromTitle(binding.value.title) + if (new_front == front) { + return + } + front = new_front + removeMathJax() + addMathJax(front) + }, + unmounted() { + removeMathJax() + } +} + +export default vMathJax diff --git a/zimui/src/main.ts b/zimui/src/main.ts index 5531a32..e598036 100644 --- a/zimui/src/main.ts +++ b/zimui/src/main.ts @@ -8,6 +8,7 @@ import router from './router' import loadVuetify from './plugins/vuetify' import ResizeObserver from 'resize-observer-polyfill' +import vMathJax from './directives/mathjax' if (typeof window.ResizeObserver === 'undefined') { console.debug('Polyfilling ResizeObserver') @@ -20,6 +21,7 @@ loadVuetify() app.use(createPinia()) app.use(vuetify) app.use(router) + app.directive('mathjax', vMathJax) app.mount('#app') }) .catch((error) => { diff --git a/zimui/src/stores/main.ts b/zimui/src/stores/main.ts index 286de30..ed1ccc5 100644 --- a/zimui/src/stores/main.ts +++ b/zimui/src/stores/main.ts @@ -27,7 +27,6 @@ export const useMainStore = defineStore('main', { this.isLoading = true this.errorMessage = '' this.errorDetails = '' - return axios.get('./content/shared.json').then( (response) => { this.isLoading = false diff --git a/zimui/src/types/global.d.ts b/zimui/src/types/global.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/zimui/src/types/mathjax.d.ts b/zimui/src/types/mathjax.d.ts new file mode 100644 index 0000000..758d9a1 --- /dev/null +++ b/zimui/src/types/mathjax.d.ts @@ -0,0 +1,9 @@ +// declare window.MathJax so that we can manipulate it without TS errors +declare global { + interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MathJax: any + } +} + +export {} diff --git a/zimui/src/views/HomeView.vue b/zimui/src/views/HomeView.vue index 3e1493a..68fa300 100644 --- a/zimui/src/views/HomeView.vue +++ b/zimui/src/views/HomeView.vue @@ -45,10 +45,12 @@ watch(
+

{{ page?.title }}

Page not found