arnog / mathlive

A web component for easy math input
https://cortexjs.io/mathlive
MIT License
1.55k stars 277 forks source link

[FEATURE] multiline input #224

Closed panoschal closed 9 months ago

panoschal commented 5 years ago

Description

In order to be able to type content across multiple lines, you must add \begin{multiline} ... \end{multiline} (#190). I think it would be better if there were an option to specify if you want to enable the multiline environment natively. In Katex, just adding \\ without any environment, specifies a newline.

Actual behavior

The problem is that if you go to mathlive.io and paste \begin{multiline} \end{multiline}, you must press the left arrow in order to move the cursor in the right position. This could be solved by using mf.perform(['insert', '\\begin{multiline} #0 \\end{multiline}']); but again, if the user clicks at the right blank side of the mathfield, they would focus outside the multiline environment. image

Expected Behavior

I think that allowing both multiline and regular input side to side, is of no use, so it would make sense if only one mode was allowed, for the whole mathfield, without the need for the user to manually add \begin{multiline} ... \end{multiline}.

rpdiss commented 5 years ago

Ye i agree with @arnog 2 many modes gonna provide many bugs it would be great if u add configuration to config that allows open only one mode many users like the multi line feature and complain that was missing regarding to other editors.

arnog commented 5 years ago

Changing the title from "Multiline support is not user-friendly" to "Feature: multiline input".

I agree this would be an interesting feature to add. Regarding the implementation, I think you would need to do the following:

johnhbenetech commented 5 years ago

@arnog Benetech also votes for this feature! We played around a bit on our side with intercepting key input and trying to manipulate the underlying LaTex on the fly - but it wasnt very stable.

stefnotch commented 4 years ago

Maybe it's also worth considering adding the \substack{} command for multiple lines.

danzyaka commented 3 years ago

Maybe it's also worth considering adding the \substack{} command for multiple lines.

don't work yet

ashayp22 commented 3 years ago

Is there any way to add content across multiple lines right now?

elisaado commented 2 years ago

I wonder if multiline support has been added

arnog commented 2 years ago

@elisaado Thank you for your interest in this feature.

I am not actively working on it right now, so if someone else want to pick it up, let me know.

The status of this issue will be updated once the feature has been implemented.

Smef commented 2 years ago

This is pretty big for high school math and would be very useful to have. GitHub needs a "Star issue" feature!

dobesv commented 2 years ago

Pasting \begin{multiline} ... \end{multiline} into a mathlive input doesn't work right now on the website or the latest npm version, FWIW.

Joewings-jw commented 2 years ago

I can attest \begin{multiline} ... \end{multiline} its not working.

elisaado commented 2 years ago

This is how I've seen it in other editors (like microsoft Word): each time you go to a new line (by pressing the return key), a new math container is created for that line, so in this case it would be

<math-field>f(x)=...</math-field>
<br>
<math-field>g(x)=...</math-field>

(well don't use br but some css to make each math-field span a new line)

Joewings-jw commented 2 years ago

We are using Mathlive to embed a virtual keyboard that enable users edit math path solutions,for this one mathfield,the desired output is to move the cursor to the next column on pressing enter.The same is achieved but horizontally,kindly assist.

evyatarbh commented 2 years ago

Over 3 years since the original request was created, any updates on this issue? Can we achieve any sort of line breaking (and height increase) in latest version, either manual or by automatic wrapping?

arnog commented 2 years ago

@evyatarbh no one has expressed interest in working on or sponsoring this feature at this point. I think it would be cool, although that's quite a bit of work to implement.

arnog commented 2 years ago

Pasting \begin{multiline} ... \end{multiline} into a mathlive input doesn't work right now on the website or the latest npm version, FWIW.

The name of the environment is multline not multiline. It's the LaTeX spelling 🤷 and it's working right now.

zanzlender commented 1 year ago

I've been playing a little bit with this... And I've succeeded only by changing the CSS a little bit... Although this was tried out in a browser so I don't know how realistic it is.

Long story short:

The math-field object has roughly these children: ML_container > ML_content > ML_mathlive > ML_base where all the content is rendered inside ML_base. Where each character or set of characters is a child of ML_base. What I've done is this:

And that's basically it... I needed to do some additional css settings like max-width, but now whenever it reaches the end of the container the next character goes into the newline.

image

Like I said this was hastily tested so I don't know if this can even work or if it's any good. Hope someone can approve and or disagree 👍

And short update - no it doesn't work if you just do the same thing in my CSS file because the math-field is inside the shadow DOM.

zanzlender commented 1 year ago

Another update. After some digging I found out you COULD style a shadow DOM element, however that element needs to have an exposed attribute part like in this example.

If you inspect the example you can see that the example component has a wrapper div which has the exposed attribute part="box".

image

Using that attribute you can style the component like so:

example-component::part(box) {
  background-color: pink;
}

However when you look at the math-field component you can see that the wrapper div has no classes and no exposed attributes (because of which you cannot style it - as far as I know for now)

image

evyatarbh commented 1 year ago

@zanzlender I actually succeeded styling the textarea in a somewhat hacky way as follows - `this.mathField = new MathfieldElement();

this.mathField.shadowRoot.innerHTML +=

"";`

Some of it was needed for our E2E tests to be able to edit the inner text (I couldn't set focus to the textarea at first) and some was to conform with our UI/UX requirements.

arnog commented 1 year ago

@zanzlender it's a good start, but there would be more work needed. For example, the selection across multiple lines doesn't work: put the caret in the middle of the first line, then shift click in the middle of the second line: the entire mathfield appears selected, although only a portion of it is). Also, the cuts are not done in the right place, for examples multi-digits numbers can get cut in half.

Also, it's true that there's no way to style the "root" <div> inside the shadow DOM. I could add a part for it, but I'm not sure why you need it?

@evyatarbh that's a creative approach, although it's not supported and might break in the future. I assume that for .ML__fieldcontainer you meant .ML__container? Note that this is addressable using math-field::part(container). The <textarea> component inside the mathfield is not addressable. Note also that it's not always present (it's not present on touch-enabled devices, for example). And I'm not clear what you were trying to do. For E2E tests, you can set the focus on the <math-field> directly and send it keydown events directly.

zanzlender commented 1 year ago

@arnog will try to think about it :) However, I would say although it may have some broken use cases for me it's better to have it like that than to have the formula disappear when it gets too big. E.g. I was trying to add a way to enter a formula inside of EditorJS by creating a plugin with Mathlive. In that case the width of the Editor can vary depending on the size of the screen, meaning that the math-field is also restrained by that. In that sense a formula that is normally visible on desktop may not be entirely visible on mobile.

And regarding the styling part... It was just my thought on how I could get around this and make it easier to style however you want. But as @evyatarbh said, it can be done without it.

image

arnog commented 9 months ago

You can now enter multiple lines by typing alt+return.

jana14pb commented 9 months ago

Great work Mr.Arno Gourdol. I'm glad to hear that!.

On Sat, Dec 9, 2023 at 4:43 AM Arno Gourdol @.***> wrote:

You can now enter multiple lines by typing alt+return.

— Reply to this email directly, view it on GitHub https://github.com/arnog/mathlive/issues/224#issuecomment-1847959223, or unsubscribe https://github.com/notifications/unsubscribe-auth/BAK3I3B66L6PGQDH26443Z3YIONJTAVCNFSM4H4NMRT2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBUG44TKOJSGIZQ . You are receiving this because you commented.Message ID: @.***>

Smef commented 9 months ago

This is great! We're going to update and do some testing soon.

molaei2000 commented 6 months ago

@arnog Thank you for adding this feature but it not working on text mode. I am working on a exam project this library save my life. I need something like textarea behavior because question's description is long. I have solved this problem for now by adding overflow: scroll to content part but it is not good approach. please add this feature.

langit commented 1 month ago

@molaei2000 This may help solve your problem: if you press enter, you will get a new line to continue typing (either math or text):

<script type="text/javascript">
mf = document.getElementById('math-Q1-Answer');
mf.addEventListener('beforeinput', (ev) => {
    //console.log(ev.inputType, ev.target);
    let imf = ev.target;
    let mode = imf.mode;
    if(ev.inputType === 'insertLineBreak') { 
        imf.executeCommand(["switchMode", "math"]);
        const cursor = "thecursor";
        const magic = `\\placeholder[${cursor}]{}`;
        const groupstt = "\\placeholder[cursorgroupstt]{}";
        const groupend = "\\placeholder[cursorgroupend]{}";
        //side-effect: break text into two parts
        imf.executeCommand(["insert", magic]); 
        imf.executeCommand(["moveToGroupStart"]); 
        imf.executeCommand(["insert", groupstt]); 
        imf.executeCommand(["moveToGroupEnd"]); 
        imf.executeCommand(["insert", groupend]); 

        let value = imf.value;
        const dslash = "\\\\ ";
        const displn = "\\displaylines";
        if (value.includes(dslash+groupstt)||value.includes(displn+groupstt)){
            value = value.replace(groupstt, " ");
            value = value.replace(groupend, "");
        }else{
            value = value.replace(groupstt, `{${displn} `);
            value = value.replace(groupend, "}");
        } 

        value = value.replace(magic, dslash + magic);
        imf.value = value;
        console.log(value);

        imf.executeCommand(["moveToMathfieldStart"]);
        for(let ph in imf.getPrompts()){
            imf.executeCommand(["moveToNextPlaceholder"]);
            if (ph == cursor) break;
        }
        imf.executeCommand(["moveToNextChar"]);
        imf.executeCommand(["deleteBackward"]);
        if(mode=='text') imf.executeCommand(["switchMode", mode]);
        ev.preventDefault();} });
</script>

The idea is to insert special placeholders for changing the latex value as needed, hope it helps.

langit commented 1 month ago

For those who are interested, this example code supports both insert and delete of newlines:

<math-field id="math-Q1-Answer" style="display: block">  </math-field> 
<script type="text/javascript">
mf = document.getElementById('math-Q1-Answer');
mf.addEventListener('beforeinput', (ev) => {
    // console.log(ev.inputType);
    let imf = ev.target;
    let mode = imf.mode;
    function restore_cursor(value) {
        imf.value = value;
        console.log(value);
        imf.executeCommand(["moveToMathfieldStart"]);
        for(let ph in imf.getPrompts()){
            imf.executeCommand(["moveToNextPlaceholder"]);
            if (ph == cursor) break;
        }
        imf.executeCommand(["moveToNextChar"]);
        imf.executeCommand(["deleteBackward"]);
        if(mode=='text') imf.executeCommand(["switchMode", mode]);
    }
    const cursor = "thecursor";
    const magic = `\\placeholder[${cursor}]{}`;
    const groupstt = "\\placeholder[cursorgroupstt]{}";
    const groupend = "\\placeholder[cursorgroupend]{}";
    const dslash = "\\\\ ";
    const displn = "\\displaylines";
    if(ev.inputType === 'insertLineBreak') { 
        imf.executeCommand(["switchMode", "math"]);
        //side-effect: break text into two parts
        imf.executeCommand(["insert", magic]); 
        imf.executeCommand(["moveToGroupStart"]); 
        imf.executeCommand(["insert", groupstt]); 
        imf.executeCommand(["moveToGroupEnd"]); 
        imf.executeCommand(["insert", groupend]); 

        let value = imf.value;
        if (value.includes(dslash+groupstt)||value.includes(displn+groupstt)){
            value = value.replace(groupstt, " ");
            value = value.replace(groupend, "");
        }else{
            value = value.replace(groupstt, `{${displn} `);
            value = value.replace(groupend, "}");
        } 

        value = value.replace(magic, dslash + magic);
        restore_cursor(value);
        ev.preventDefault();    
    }else if(ev.inputType === 'deleteContentBackward'){
        imf.executeCommand(["switchMode", "math"]);
        //side-effect: break text into two parts
        imf.executeCommand(["insert", magic]); 
        let value = imf.value;
        if (value.includes(dslash+magic)){
            value = value.replace(dslash+magic, magic);
            ev.preventDefault();    
        }
        restore_cursor(value);
    }else if(ev.inputType === 'deleteContentForward'){
        imf.executeCommand(["switchMode", "math"]);
        //side-effect: break text into two parts
        imf.executeCommand(["insert", magic]); 
        let value = imf.value;
        if (value.includes(magic+dslash)){
            value = value.replace(magic+dslash, magic);
            ev.preventDefault();    
        }        
        restore_cursor(value);
    }
});
</script>

Cheers!