I'm looking into building an editor with Slate or ProseMirror and seeing some strange behavior with Chrome around the caret position and selection area when using an inline element. Issue 1 is shown in the first picture below. When the text cursor position is behind the "f", the caret is shown at the top of the image. Issue 2 is in the second image - selecting the text shows a highlighted area that's as tall as in the inlined element. Is there any way to control this behavior and instead have the caret show at the position of the text and only highlight the space around the text (even if the inline image is making the line height larger)
I'd like to mimic the behavior here, from firefox:
Example images were produced using the ProseMirror demo here: https://prosemirror.net/examples/basic/
A minimum example (thanks @Kaiido) with JSBin:
<div contenteditable>Test text<img src="https://upload.wikimedia.org/wikipedia/en/9/9b/Yoda_Empire_Strikes_Back.png">Testing</div>
Not sure how this behaves on other operating systems, but I'm using macOS Catalina.
This code works for IE 6, 7, 8, >=9, Edge, Chrome, Safari, Firefox and Opera. Type the number where you want to set the caret position and click on “Set Position” button. Type the starting character number and end character number. Then click on “Set Selection” button to set selection. Click on “Get Position/Selection”.
How to set caret position Type the number where you want to set the caret position and click on “Set Position” button. How to set selection Type the starting character number and end character number. Then click on “Set Selection” button to set selection.
Click on “Get Position/Selection”. If there is no selection (i.e. selection is collapsed), you will see only single value. This value represents current caret position. If there is a selection, you will see start and end position for selection.
How to get caret position or selection Click on “Get Position/Selection”. If there is no selection (i.e. selection is collapsed), you will see only single value. This value represents current caret position.
You can fix the selection problem by using flexbox. I originally tried using align-items: flex-end
but I was getting some weird arrow key behavior. align-items: baseline
seems to work as of now.
The image problem is very strange. I noticed that Chrome steps over SVG elements differently than IMG elements. As of now, the latest version of Chrome "waits" before skipping over IMG, but allows the user to skip over an SVG like any other character (left arrow skips character closest to svg). This may be caused by underlying default styles, but I do not know.
I was about to post my findings, but I realized that Firefox did not work the same. Firefox has some really weird arrow key behavior when using SVG and/or Flexbox. The cursor goes above the image, but only when pressing the right arrow key.
However, Firefox works just fine with a normal IMG element.
Long story short, you will have to render different image elements based on the browser.
// chrome contenteditable ads spaces between spans.
// firefox does not, so you have to add them manually.
if (navigator.userAgent.indexOf("Firefox") > 0) {
Array.from(document.querySelectorAll('#editable span')).forEach(el => {
el.innerHTML += " "
})
}
.flexit {
display: flex;
align-items: baseline;
flex-wrap: wrap;
}
span {
display: inline-block;
white-space: pre;
}
.img-wrapper, .img-wrapper + span {
display: inline;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<h1>chrome</h1>
<div contenteditable="true" class="flexit">
<span>test</span><svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/>
</svg><span>Here</span><span>is</span><span>a</span><span>longer</span><span>sentence.</span><span>Notice</span><span>I</span><span>wrapped</span><span>each</span><span>word</span><span>in</span><span>a</span><span>span.</span><span>This</span><span>makes</span><span>the</span><span>text</span><span>appear</span><span>like</span><span>it</span><span>is</span><span>inline.</span>
</div>
<h1>firefox</h1>
<div contenteditable="true" id="editable">
<span>test</span><span class="img-wrapper"><img src="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200" /></span><span>test</span><span>Here</span><span>is</span><span>a</span><span>longer</span><span>sentence.</span><span>Notice</span><span>I</span><span>wrapped</span><span>each</span><span>word</span><span>in</span><span>a</span><span>span.</span><span>This</span><span>makes</span><span>the</span><span>text</span><span>appear</span><span>like</span><span>it</span><span>is</span><span>inline.</span>
</div>
</body>
</html>
P.S. A lot of editors I have used default to full-width images.
Edit: I just realized that it appears my Firefox solution also works in the latest Chrome. I think this works because I wrapped the text nodes in SPAN elements. The SVG element still works differently in Chrome, but wrapping text in SPAN seems to solve most of the problems.
First, don't manipulate the ProseMirror DOM as shown in the JQuery example. In fact you most likely will run into DOM or content issues. ProseMirror uses its own DOM node and markup schema. If you want to manipulate the ProseMirror DOM or add plugin then take a look at the Markup, Plugin & Node Apis. Ive attached a simple example of text align Markup code. Side note, the reason Grammarly and others don't have ProseMirror plugins is because of the DOM approach / models. I should add the ProseMirror is very good, but to be honest it is more of an advanced developer solution.
That aside, the good news the problems you have a purely CSS. ProseMirror strips out all classes and resets the DOM / CSS so if you import a document or cut and paste all / most your classes will be gone.
Simplest approach to solve that is to wrap the editor in a div and assign a class to the div then add styles and child styles to that class. The reason to wrap it is that the css selectors such as img, p, h, etc will only apply to the tags inside of the editor class. Without that you end up with obvious CSS clashes.
CSS
float: left;
(recommended approach) vertical-align: baseline;
but without floats you will still have a cursor that matches the height of the image and not the text. Also, if you don't use floats the cursor will be elongated as the line height is effectively the same height as the image. The blue color is just selector, which you change using CSS as well. <html>
<div class="editor">
<!-- <editor></editor>-->
</div>
</html>
<style>
.editor {
position: relative;
display: block;
padding: 10px;
}
.editor p {
font-weight: 400;
font-size: 1rem;
font-family: Roboto, Arial, serif;
color: #777777;
display: inline;
vertical-align: baseline;
}
.editor img {
width: 50px;
float: left;
padding: 20px;
}
</style>
Node extension example
Example of Node extension for text align that can be added as a toolbar. Much longer post, but even if you did create a Node / plugin for images you have to deal with the manner in which it render i.e. base64 versus url, etc. which btw, makes perfect sense as to why they did that, but just add complexity to developers looking for SEO, etc.
export default class Paragraph extends Node {
get name() {
return 'paragraph';
}
get defaultOptions() {
return {
textAlign: ['left', 'center', 'right'],
}
}
inputRules({ type }) {
return [
markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/, type),
]
}
get schema() {
return {
attrs: {
textAlign: {
default: 'left'
}
},
content: 'inline*',
group: 'block',
draggable: false,
inclusive: false,
defining : true,
parseDOM: [
{
tag: 'p',
style: 'text-align',
getAttrs: value => value
}
],
toDOM: (node) => [ 'p', {
style: 'text-align:' + node.attrs.textAlign,
class: `type--base type--std text-` + node.attrs.textAlign
}, 0 ]
};
}
commands ({ type }) {
return (attrs) => updateMark(type, attrs)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With