diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ddcdc9..b842ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Changelog -## unreleased +## [unreleased] -- More soon. +## 0.1.0 + +- Added final `Mathfield component` + +- Published fleshed-out documentation notebook at https://mentat-collective.github.io/mathlive.cljs + +- Upgraded `mathlive` to `0.86.0` and `compute-engine` to `0.12.2` ## 0.0.1 -First release! +- First release! diff --git a/build.clj b/build.clj index 8c18720..03f8daf 100644 --- a/build.clj +++ b/build.clj @@ -5,7 +5,7 @@ ;; ## Variables (def lib 'org.mentat/mathlive.cljs) -(def version "0.0.1") +(def version "0.1.0") (defn- ->version ([] version) diff --git a/dev/mathlive/clerk_ui.cljc b/dev/mathlive/clerk_ui.cljc index 8435dac..79394b7 100644 --- a/dev/mathlive/clerk_ui.cljc +++ b/dev/mathlive/clerk_ui.cljc @@ -1,5 +1,6 @@ (ns mathlive.clerk-ui - (:require #?(:cljs [mathlive.core]) + (:require #?(:cljs ["@cortex-js/compute-engine"]) + #?(:cljs [mathlive.core]) #?(:clj [nextjournal.clerk :as clerk]) #?(:cljs [nextjournal.clerk.sci-viewer :as sv]) #?(:cljs [sci.core :as sci])) diff --git a/dev/mathlive/notebook.clj b/dev/mathlive/notebook.clj index 5ab55b1..6c9be66 100644 --- a/dev/mathlive/notebook.clj +++ b/dev/mathlive/notebook.clj @@ -134,7 +134,12 @@ math-field:focus-within { :on-change on-change}] [:h4 "Text Area"] [:textarea - {:style {:width "100%" :border "0.5px solid"} + {:style + {:width "100%" + :border "1px solid" + :border-radius "4px" + :font-size "20px" + :padding "8px"} :value @!tex :on-change on-change}]])) @@ -294,6 +299,50 @@ math-field:focus-within { assoc "cake" "\\Gamma"))}]) +;; ### Styling +;; +;; From MathLive's [guide on customization](https://cortexjs.io/mathlive/guides/customizing/): +;; +;; > The appearance and behavior of the mathfield is highly customizable. +;; +;; The `Mathfield` instances in this notebook have all been customized with the following styles: +;; +;;```css +;; math-field { +;; font-size: 24px; +;; border-radius: 4px; +;; border: 1px solid; +;; padding: 8px; +;; } +;; math-field:focus-within { +;; outline: none; +;; border: 1px solid blue; +;; } +;;``` +;; +;; ### Fonts and Sounds +;; +;; By default, `Mathfield` instances attempt to load fonts and sounds from +;; `/dist/fonts` and `/dist-sounds`, respectively, of the route serving the +;; `Mathfield`. +;; +;; You can customize fonts with a `"fonts-directory"` attribute or +;; `fontsDirectory` option. See the [fonts +;; guide](https://cortexjs.io/mathlive/guides/customizing/#fonts) for more +;; details. +;; +;; To customize the sounds location, use the `"sounds-directory"` attribute or +;; `soundsDirectory` option. +;; +;; For more details on both of these options, see +;; the [`CoreOptions`](https://cortexjs.io/docs/mathlive/#(CoreOptions%3Atype)) +;; documentation. +;; +;; If you want to set these without serving them yourself, `mathlive.cljs` +;; provides and `mathlive.core/cdn-fonts` and `mathlive.core/cdn-sounds` vars +;; that point to the CDN distribution of the currently loaded version of +;; `mathlive`. + ;; ## MathJSON ;; ;; The default value format for a `Mathfield` is `"latex"`, but the component @@ -306,7 +355,21 @@ math-field:focus-within { ;; tooling to extract MathJSON from a `Mathfield` parsed into Clojure data ;; structures. ;; -;; This example pulls both LaTeX and MathJSON from a `Mathfield`. +;; To use any of the MathJSON features you'll need to require the +;; `@cortex-js/compute-engine` dependency somewhere in your codebase: +;; +;; ```clj +;; (ns my-app +;; (:require [mathlive.core :as ml] +;; [reagent.core :as reagent] +;; ;; included for side effects +;; ["@cortex-js/compute-engine"])) +;; ``` +;; +;; ### Extracting MathJSON +;; +;; The following example example pulls both LaTeX and MathJSON from a +;; `Mathfield`. ;; ;; First, create a `reagent/atom` to store the values: diff --git a/dev/user.clj b/dev/user.clj index 665ae97..1d9259f 100644 --- a/dev/user.clj +++ b/dev/user.clj @@ -32,8 +32,8 @@ (clerk/show! "dev/mathlive/notebook.clj")) (defn github-pages! [_] - ;; TODO this now defaults to a project page. Do we want to change this? - (swap! config/!resource->url merge {"/js/viewer.js" "/mathlive.cljs/js/main.js"}) + (swap! config/!resource->url merge + {"/js/viewer.js" "/mathlive.cljs/js/main.js"}) (clerk/build! {:index "dev/mathlive/notebook.clj" :bundle? false diff --git a/package-lock.json b/package-lock.json index 01bda69..be98374 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "mathlive.cljs", - "version": "0.0.1", + "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mathlive.cljs", - "version": "0.0.1", + "version": "0.1.0", "dependencies": { - "@cortex-js/compute-engine": "^0.12.1", - "@mentatcollective/mathlive": "0.85.2" + "@cortex-js/compute-engine": "^0.12.2", + "mathlive": "^0.86.0" }, "devDependencies": { "@babel/core": "^7.17.9", @@ -394,9 +394,9 @@ } }, "node_modules/@cortex-js/compute-engine": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@cortex-js/compute-engine/-/compute-engine-0.12.1.tgz", - "integrity": "sha512-oOb8Wn0vbI1agJt/vW/4AnIOvyUYifpQItF7YB9bsQCgJ6iqzfYGTTb79gBns0BVzJL/GWap/fByNjMNP4soBQ==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@cortex-js/compute-engine/-/compute-engine-0.12.2.tgz", + "integrity": "sha512-MxXYwCsHzcuzpC81khhp+r0/PN6gO8/Kb9kDeATFUwGqDU0sfejskvmiblQI5xlc323WVSGQOXNemqHFBgCx5g==", "dependencies": { "complex.js": "^2.1.1", "decimal.js": "^10.4.0" @@ -476,19 +476,6 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@mentatcollective/mathlive": { - "version": "0.85.2", - "resolved": "https://registry.npmjs.org/@mentatcollective/mathlive/-/mathlive-0.85.2.tgz", - "integrity": "sha512-gHPAunh+9P6n/jfqtZATQvRrkLq2Fd5UnEXcMqQzDTZNZ0NN8ZFJX1VFsQagKyNNWXxwcR1O40Kb1ekePiQ42A==", - "engines": { - "node": ">=16.14.2", - "npm": ">=8.5.0" - }, - "funding": { - "type": "individual", - "url": "https://paypal.me/arnogourdol" - } - }, "node_modules/@motionone/animation": { "version": "10.14.0", "dev": true, @@ -2445,6 +2432,19 @@ "three": ">=0.118.0" } }, + "node_modules/mathlive": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/mathlive/-/mathlive-0.86.0.tgz", + "integrity": "sha512-gChz82gnK0mVbTkmuomwAFSPZTERicBw7hi60J1XqYhbYL+qvUzRw6gBEAIxMOs7pL+6EXPJI7J+QWni/i05lQ==", + "engines": { + "node": ">=16.14.2", + "npm": ">=8.5.0" + }, + "funding": { + "type": "individual", + "url": "https://paypal.me/arnogourdol" + } + }, "node_modules/md5.js": { "version": "1.3.5", "dev": true, @@ -3930,9 +3930,9 @@ } }, "@cortex-js/compute-engine": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@cortex-js/compute-engine/-/compute-engine-0.12.1.tgz", - "integrity": "sha512-oOb8Wn0vbI1agJt/vW/4AnIOvyUYifpQItF7YB9bsQCgJ6iqzfYGTTb79gBns0BVzJL/GWap/fByNjMNP4soBQ==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@cortex-js/compute-engine/-/compute-engine-0.12.2.tgz", + "integrity": "sha512-MxXYwCsHzcuzpC81khhp+r0/PN6gO8/Kb9kDeATFUwGqDU0sfejskvmiblQI5xlc323WVSGQOXNemqHFBgCx5g==", "requires": { "complex.js": "^2.1.1", "decimal.js": "^10.4.0" @@ -3993,11 +3993,6 @@ "@lezer/common": "^1.0.0" } }, - "@mentatcollective/mathlive": { - "version": "0.85.2", - "resolved": "https://registry.npmjs.org/@mentatcollective/mathlive/-/mathlive-0.85.2.tgz", - "integrity": "sha512-gHPAunh+9P6n/jfqtZATQvRrkLq2Fd5UnEXcMqQzDTZNZ0NN8ZFJX1VFsQagKyNNWXxwcR1O40Kb1ekePiQ42A==" - }, "@motionone/animation": { "version": "10.14.0", "dev": true, @@ -5369,6 +5364,11 @@ "lodash": "^4.17.21" } }, + "mathlive": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/mathlive/-/mathlive-0.86.0.tgz", + "integrity": "sha512-gChz82gnK0mVbTkmuomwAFSPZTERicBw7hi60J1XqYhbYL+qvUzRw6gBEAIxMOs7pL+6EXPJI7J+QWni/i05lQ==" + }, "md5.js": { "version": "1.3.5", "dev": true, diff --git a/package.json b/package.json index d8437a8..b76c087 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "mathlive.cljs", - "version": "0.0.1", + "version": "0.1.0", "dependencies": { - "@cortex-js/compute-engine": "^0.12.1", - "@mentatcollective/mathlive": "0.85.2" + "@cortex-js/compute-engine": "^0.12.2", + "mathlive": "^0.86.0" }, "devDependencies": { "@babel/core": "^7.17.9", diff --git a/src/deps.cljs b/src/deps.cljs index 78ca9c1..bb9ecd0 100644 --- a/src/deps.cljs +++ b/src/deps.cljs @@ -1,3 +1,3 @@ {:npm-deps - {"@cortex-js/compute-engine" "^0.12.1", - "@mentatcollective/mathlive" "0.85.2"}} + {"@cortex-js/compute-engine" "^0.12.2", + "mathlive" "^0.86.0"}} diff --git a/src/mathlive/core.cljs b/src/mathlive/core.cljs index 9cf3270..dac3426 100644 --- a/src/mathlive/core.cljs +++ b/src/mathlive/core.cljs @@ -4,8 +4,7 @@ associated utilities." (:require [goog.object :as obj] [reagent.core :as r] - ["@cortex-js/compute-engine" :refer [ComputeEngine]] - ["@mentatcollective/mathlive/dist/mathlive.js" #_#_ :as ml] + ["mathlive" :as ml] ["react" :as react])) ;; ## Utilities @@ -14,7 +13,7 @@ "Given a `MathfieldElement` `mf` and either a - map of keyword-or-string => option - - function from current options => new options + - function from Clojurized current options => new options Calls [`mf.setOptions`](https://cortexjs.io/docs/mathlive/#(Mathfield%3Ainterface).setOptions) with @@ -26,10 +25,6 @@ (fn? opts-or-f) (-> (.getOptions mf) (js->clj :keywordize-keys true) - ;; TODO once this is resolved: - ;; https://github.com/arnog/mathlive/issues/1738, we can - ;; erase this. - (dissoc :computeEngine) (opts-or-f)) (map? opts-or-f) opts-or-f :else (throw @@ -40,38 +35,41 @@ nil)) (def ^{:doc "Currently loaded version of -the [mathlive](https://www.npmjs.com/package/mathlive) npm package. "} +the [mathlive](https://www.npmjs.com/package/mathlive) npm package."} mathlive-version - "0.85.1" - ;; TODO enable this again once we get back to mainline mathlive vs our fork. - #_(.-mathlive ml/version)) - - + (.-mathlive ml/version)) (def ^{:doc "Location of the `sounds` directory in the CDN-served package - of [mathlive](https://www.npmjs.com/package/mathlive)."} - cdn-sounds + of [mathlive](https://www.npmjs.com/package/mathlive)."} cdn-sounds (str "https://unpkg.com/mathlive@" mathlive-version "/dist/sounds/")) (def ^{:doc "Location of the `fonts` directory in the CDN-served package - of [mathlive](https://www.npmjs.com/package/mathlive)."} - cdn-fonts + of [mathlive](https://www.npmjs.com/package/mathlive)."} cdn-fonts (str "https://unpkg.com/mathlive@" mathlive-version "/dist/fonts/")) (defn ->math-json "Given a [`MathfieldElement`](https://cortexjs.io/docs/mathlive/#(MathfieldElement%3Aclass)) `mf`, returns a [MathJSON](https://cortexjs.io/math-json/) - representation (parsed into Clojure) of the currently displayed expression." + representation (parsed into Clojure) of the currently displayed expression. + + NOTE that for this or anything MathJSON related to work you'll need + to `(require '[@cortex-js/compute-engine])`. If this package isn't loaded, + returns `nil`." [mf] - (js->clj - (.-json ^js (.-expression mf)))) + (when-let [e (.-expression ^js mf)] + (js->clj + (.-json e)))) (defn ->placeholders "Given a [`MathfieldElement`](https://cortexjs.io/docs/mathlive/#(MathfieldElement%3Aclass)) `mf`, returns a map of (string) placeholder name => current value of the - placeholder." + placeholder. + + NOTE that for `{:type \"math-json\"}` to work you'll need to `(require + '[@cortex-js/compute-engine])`. If this option is specified and the package + isn't loaded, returns `nil` for each placeholder value." ([mf] (->placeholders mf {})) ([mf {:keys [type] :or {type "latex"}}] @@ -85,16 +83,17 @@ the [mathlive](https://www.npmjs.com/package/mathlive) npm package. "} {} (js-keys m))))) -(def ^:no-doc engine - (ComputeEngine.)) - (defn math-json->tex "Given a Clojure data structure `expr` representing a [MathJSON](https://cortexjs.io/math-json/) expression, returns a string of - LaTeX representing `expr`." + LaTeX representing `expr`. + + NOTE that for this or anything MathJSON related to work you'll need + to `(require '[@cortex-js/compute-engine])`. If this package isn't loaded, + returns `nil`." [expr] - (.-latex - (.box engine (clj->js expr)))) + (ml/serializeMathJsonToLatex + (clj->js expr))) (defn set-math-json! "Given @@ -104,10 +103,15 @@ the [mathlive](https://www.npmjs.com/package/mathlive) npm package. "} sets the value of `mf` to the TeX version of `expr`. - Equivalent to `(.setValue mf (math-json->tex expr))`." + Equivalent to `(.setValue mf (math-json->tex expr))`. + + NOTE that for this or anything MathJSON related to work you'll need + to `(require '[@cortex-js/compute-engine])`. If this package isn't loaded, + this command has no effect." [mf expr] - (set! ^js (.-expression mf) - (clj->js expr))) + (when (.-expression ^js mf) + (set! (.-expression ^js mf) + (clj->js expr)))) ;; ## Reagent Component ;; @@ -118,55 +122,65 @@ the [mathlive](https://www.npmjs.com/package/mathlive) npm package. "} ;; [:math-field {:on-input (fn [x] )} ;; "1+x"] ;; ``` -;; -;; This version makes a few changes that I will document soon! -;; -;; TODO document changes. - -;; - Change to :default-value and :value, log a warning under "error" if children are set. -;; - docs for all of the field things from html, https://cortexjs.io/docs/mathlive/?q=fints-dire, options +(def ^{:doc "Reagent component around + the [MathLive](https://github.com/arnog/mathlive) equation editor. -(def ^{:doc "Docstring for the Mathfield."} + NOTE: Following React's convention, `:on-change` binds a listener to to the + `input` event. See https://reactjs.org/docs/dom-elements.html#onchange"} Mathfield (r/adapt-react-class (react/forwardRef (fn [props ref] (let [[mf set-mf] (react/useState nil) {:strs [children value options defaultValue onChange - onPlaceholderChange - soundsDirectory fontsDirectory] :as props} + onPlaceholderChange] :as props} (js->clj props)] - ;; These effects run once on initial load (note the empty dependency array). + ;; These effects run once on initial load (note the empty dependency + ;; array). They provide warnings similar to the warnings you see from a + ;; `:textarea`. (react/useEffect (fn mount [] (when children - (js/console.error "don't set children!")) + (js/console.error + (str "Warning: use the `:default-value` or `:value` " + "props instead of setting children on `Mathfield`."))) (when (and defaultValue value) - (js/console.error "don't both value and defaultValue!"))) + (js/console.error + (str "Warning: don't set both `:value` and `:default-value` props." + " `Mathfield`s must be either controlled or uncontrolled" + " (specify either the `:value` prop, or the `:default-value` prop, but not both)." + " Decide between using a controlled or uncontrolled `Mathfield` and remove one of these props.")))) #js []) - ;; NOTE this tricky thing, don't use "options" since that changes every - ;; time! This effect notes if any properties change; this matters if you - ;; allow the user to provide a map. + ;; NOTE We have to use this trick to prevent effects that depend on + ;; `options` from re-running on every re-render. This is because JS + ;; objects use reference equality, not value equality. This trick lets + ;; us only reset when the previous and next are `not=`, which uses + ;; Clojure's value equality comparison. (let [opt-ref (react/useRef options)] (when (not= (.-current opt-ref) options) (set! (.-current opt-ref) options)) (react/useEffect + ;; Only run when the VALUE of `options` changes, not the reference. (fn mount [] (when (and mf options) (update-options! mf options))) #js [(.-current opt-ref) mf])) + ;; This effect updates the value of the `Mathfield` if it's changed from + ;; somewhere else. (react/useEffect (fn [] (when (and mf value (not= (.getValue mf) value)) (.setValue mf value))) #js [mf value]) + ;; React doesn't pick up `:on-placeholder-change` so we intercept it and + ;; do the right thing here. (react/useEffect (fn mount [] (when (and mf onPlaceholderChange) @@ -176,9 +190,11 @@ the [mathlive](https://www.npmjs.com/package/mathlive) npm package. "} (.removeEventListener mf "placeholder-change" onPlaceholderChange)))) #js [mf onPlaceholderChange]) - ;; For whatever reason, when the component is controlled, the component - ;; can't move its own cursor position well. So this handles the odd case - ;; at the very beginning... + ;; NOTE: For whatever reason, when the component is controlled, the + ;; component can't move its own cursor position well. So this handles + ;; the odd case at the very beginning... Thanks to [Chris + ;; Chudzicki](https://github.com/ChristopherChudzicki/math3d-next/blob/838369956a0bd1f126f8c04ef900eaf53741011c/client/src/util/components/MathLive/MathField.tsx#L52-L69) + ;; for figuring this one out. (react/useEffect (fn [] (when (and mf @@ -186,7 +202,7 @@ the [mathlive](https://www.npmjs.com/package/mathlive) npm package. "} (= 2 (.-position mf))) (.executeCommand ^js mf "moveToPreviousWord")))) - ;; this passes `mf`back out as reference when it changes. + ;; This passes `mf`back out to `ref` when it changes. (react/useImperativeHandle ref (fn [] mf)) (r/as-element @@ -194,10 +210,5 @@ the [mathlive](https://www.npmjs.com/package/mathlive) npm package. "} (assoc (dissoc props "onPlaceholderChange" "onChange" "defaultValue" "value" "options") "children" (or value defaultValue "") - "ref" (fn [field] - (when field - (.setOptions field #js {:computeEngine (ComputeEngine.)})) - (set-mf field)) - "onInput" onChange - "sounds-directory" (or soundsDirectory cdn-sounds) - "fonts-directory" (or fontsDirectory cdn-fonts))])))))) + "ref" set-mf + "onInput" onChange)]))))))