hgiesel / closet

The Web Framework for Flashcards
https://closetengine.com
GNU General Public License v3.0
65 stars 6 forks source link

Feature Request: Closet inside Mathjax #39

Open sandersantema opened 3 years ago

sandersantema commented 3 years ago

What I mean by closet inside Mathjax is the following:

\( \square [[c1::( \square \varphi \to \psi ) ]] \lor \square ( \square \psi \to \varphi ) \)

Why? Using native anki clozes and closet clozes) you can do this without requiring a cloze inside of the mathjax formula:

\(\forall \varphi, \psi ( \mathcal{F}\models\){{c1::\(\square ( \square \varphi \to \psi ) \lor \square ( \square \psi \to \varphi) ) \)}} \( ) \)

With the corresponding render: image

This however has quite some disadvantages, namely a lot of extra cruft to split one mathjax formula into multiple mathjax formulas, not being able to use \left( \right (and other variants) and incorrect alignment of symbols as can be seen in the screenshot above and more clearly here:

In this case the spacing is incorrect, in latex the \models symbol is aligned better too I believe, such that the following symbol is centered on the y-axis but this is a more general mathjax/ anki mathjax bug I believe. Incorrect: image

Correct: image

I could imagine that this would be impossible or either very hard to implement, it would however certainly be very nice.

sandersantema commented 3 years ago

This should have the enhancement label but I don't know how to change it from the bug label.

hgiesel commented 3 years ago

This issue is actually dependent on #35. Once Closet actually executes before MathJax, it will be pretty easy:

\( 1 + 2 = [[jaxc:: 3 :: \lt 10 ]] \)

should become for the front:

\( 1 + 2 = \color{lightblue}{ \left [ \lt 10 \right ] } \)

and for the back:

\( 1 + 2 = \color{lightblue}{ \left [ 3 \right ] } \)

and without a hint, it should \color{lightblue}{ \left [ \cdots \right ] }. This will be a special setup, and not be built-in to the default c cloze, but rather be a special jaxc (or something like that) command.

35 will be implemented for AnkiDesktop with the coming v2.1.36, for AnkiDroid and AnkiMobile, it will take a bit longer. If you want to, we can give it a go then.

sandersantema commented 3 years ago

That sounds great! I'm already running the beta so I could do some preliminary testing when you start implementing this feature.

hgiesel commented 3 years ago

I just tried it out, and it works quite wonderfully. Another thing you can do now, is to provide "filler" values, for placeholders that you have in your MathJax equation.

Screenshot 2020-11-29 at 14 13 27

In the equation you can see the jaxc command, which is the MathJax cloze command. The 0 means, that this cloze is always the active cloze (because you don't use a different cloze for other cards, but rather, you switch out the values dynamically).

The setl down in the "cmds" fields, specifies the values to act replace the placeholders. The pi commands are the placeholders in the equation. The [[pi0::v]] "picks" the first (zero-indexed) replacement value from the setl.

This is the code, that defines the jaxc family set of commands.

function mathjaxCloze(closet, filterManager) {

const ellipsis = `\\cdots`

const wrapWithBrackets = (v) => `\\left [ ${v} \\right ]`

const inactiveEllipser = () => wrapWithBrackets(ellipsis)

const backStylizer = closet.Stylizer.make({
    processor: v => `\\color{cornflowerblue}{ ${v} }`,
})

const frontStylizer = backStylizer.toStylizer({
    mapper: wrapWithBrackets,
})

const frontEllipser = (tag) => ([
    tag.values[1]
    ? tag.values[1]
    : ellipsis
])

filterManager.install(
    closet.recipes.cloze({
        tagname: 'jaxc',
        inactiveEllipser,
        frontStylizer,
        backStylizer,
        frontEllipser,
    })
)

NOTE: I haven't uploaded that version to AnkiWeb yet, because I need to implement some things for future backwards compatibility first, so have some more patience please.

hgiesel commented 3 years ago

This is now supported on Anki 2.1.36+ and AnkiDroid 2.15alpha17+. AnkiMobile does not have onUpdateHook implemented, which is necessary for this.

sandersantema commented 3 years ago

I finally got around to trying this it works perfectly using anki 2.1.39beta1. I've fixed some small things in the example code you posted above. For anyone else looking at this, you can replace your whole closet setup with the following code and then clozes such as \(x = [[jaxc0::y]]\) will work. Do note that clozes such as \( x = \left( [[jaxc0::y \right)]]\) don't work i.e. latex commands such as \left(...\right) still need to be together in either the cloze or outside of the cloze inside of the encapsulating mathjax formula.

Replace your Closet Setup with the following code:

const elements = closet.anki.getQaChildNodes()
const memory = chooseMemory('closet__1')
const filterManager = closet.FilterManager.make(preset, memory.map)

const output = [[
    elements,
    memory,
    filterManager,
]]

// Use ldots instead of cdots such that ellipsis are located at the bottom of 
// the line in the same way anki's default cloze ellipsis are.
const ellipsis = `\\ldots`
const wrapWithBrackets = (v) => `\\left [ ${v} \\right ]`
const inactiveEllipser = () => wrapWithBrackets(ellipsis)

const backStylizer = closet.Stylizer.make({
    // See: https://docs.mathjax.org/en/latest/input/tex/extensions/color.html
    processor: v => `{\\color{cornflowerblue}{ ${v} }}`,
})

const frontStylizer = backStylizer.toStylizer({
    mapper: wrapWithBrackets,
})

const frontEllipser = (tag) => ([
    tag.values[1]
    ? tag.values[1]
    : ellipsis
])

filterManager.install(
    closet.recipes.shuffle({ tagname: 'mix' }),
    closet.recipes.order({ tagname: 'ord' }),
    closet.recipes.multipleChoice({ tagname: 'mc' }),
    closet.browser.recipes.rect({ tagname: 'rect' }),
    closet.recipes.cloze({ tagname: 'c' }),
    closet.recipes.cloze({
        tagname: 'jaxc',
        inactiveEllipser,
        frontStylizer,
        backStylizer,
        frontEllipser,
    })
)

@hgiesel what would be the proper way in adding this to close such that I can for example simply add closet.recipes.mathjaxCloze({tagname: 'jaxc'}) to the filterManager? Might it be a good idea to add this to closet by default? Could you possibly tell me how I'd build the extension from source and add it to anki, that way I could try to add it myself.