In DraftJs How to re adjust the cursor after setting the state via decorator and strategy? - draftjs

Problem: Loses focus when selected the hash tag.
Example: type #on and select any tag the editor is losing the focus.
In above codepen: how can I add/retain cursor after the hash tag?
Following this article
https://bigbite.net/2017/12/13/building-editor-draft-js-react/
Code from this article.
https://codepen.io/bigbite/pen/gXNOvz
I've to add that in my own use case.
UC: While editing the content in editor when user presses the key combination then show a dropdown, which will contain the custom react component name, and user will be able to select the custom component and it will add that component via decorator and strategy.
It has been achieved but the editor loses its focus at the same time.
I can achieve the focus via ref this.editor.focus() but shows the cursor at the start of the editor.
const addEntityAndComponent = (editorState, content) => {
const contentState = editorState.getCurrentContent();
const selection = editorState.getSelection();
const contentStateWithEntity = contentState.createEntity(content, 'IMMUTABLE', { content });
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newContentState = Modifier.insertText(contentStateWithEntity, selection, content, null, entityKey);
const newEditorState = EditorState.push(editorState, newContentState);
return EditorState.forceSelection(newEditorState, newContentState.getSelectionAfter());
};
I'm using the lib to achieve the functionality.
https://github.com/jpuri/react-draft-wysiwyg/
Here is my code.
https://github.com/iozeey/react-draft-wysiwyg-custom-component
Hope it will be helpful.
Followed this doc.
https://draftjs.org/docs/advanced-topics-managing-focus
Tried solution from here but did not work.
https://draftjs.org/docs/api-reference-editor-state#movefocustoend

I was using this package.
https://github.com/jpuri/react-draft-wysiwyg/blob/master/src/Editor/index.js#L348
My solution was to reset the cursor position after focusing the editor.
focus() {
const se = this.state.editorState.getSelection();
this.editor.focus();
this.setState({
editorState: EditorState.forceSelection(this.state.editorState, se),
});
}

Related

how to make snippets auto complete in my custom vs code extension?

im korean beginner developer... help... T^T
i want to make auto complete like snippets in my custom vs code extension.
now my code,
const snippetBase = `axios.({
url: "${service.url}",
method: "${service.description}",
`
const editor = vscode.window.activeTextEditor
const selection: any = editor?.selection
editor?.edit(builder => {
builder.replace(
selection,
snippetBase + snippetHeaders(snippetRes.data.header) + snippetBody(snippetRes.data.body)
)
})
then, my extension auto complete image
I want to focus my cursor automatically inside the format after the snippet is shown.
Just like picture below.
snippets image
help me plz!!
It sounds from your comment that you want the edit to act as a snippet. Then you need to make a SnippetString and use the editor.insertSnippet() command which can take that SnippetString as an argument. This should work for you:
const editor = vscode.window.activeTextEditor
const selection = editor?.selection
const axiosSnippet = new vscode.SnippetString("axios.({\n")
axiosSnippet.appendText("\turl: \"")
axiosSnippet.appendPlaceholder(service.url)
axiosSnippet.appendText("\",\n")
axiosSnippet.appendText("\tmethod: \"")
axiosSnippet.appendPlaceholder(service.description)
axiosSnippet.appendText("\",\n")
editor.insertSnippet(axiosSnippet, selection)
axiosSnippet.appendPlaceholder(service.url); is the key to this, as it will select that placeholder first and then allow you to tab to the next placeholder axiosSnippet.appendPlaceholder(service.description).
[I don't know the structure of the rest of your snippet,e.g., snippetHeaders(snippetRes.data.header). If it is just text than you can use the appendText method with \n for newlines.

How to search for and highlight a substring in Codemirror 6?

I'm building a simple code editor to help children learn HTML. One feature I'm trying to add is that when users mouseover their rendered code (in an iframe), the corresponding HTML code in the editor is highlighted. So, for example, if a user mouses-over an image of kittens, the actual code, , would be highlighted in the editor.
Mousing-over the iframe to get the html source for that element is the easy part, which I've done (using document.elementFromPoint(e.clientX, e.clientY in the iframe itself, and posting that up to the parent) - so that's not the part I need help with. The part I can't figure out is how to search for and highlight that string of selected code in the code editor.
I'm using Codemirror 6 for this project, as it seems as it will give me the most flexibility to create such a feature. However, as a Codemirror 6 novice, I'm struggling with the documentation to find out where I should start. It seems like the steps I need to complete to accomplish this are:
Search for a range in the editor's text that matches a string (ie.'<img src="kittens.gif"').
Highlight that range in the editor.
Can anyone out there give me some advice as to where in the Codemirror 6 API I should look to start implementing this? It seems like it should be easy, but my unfamiliarity with the Codemirror API and the terse documentation is making this difficult.
1. Search for a range in the editor's text that matches a string (ie.'<img src="kittens.gif"').
You can use SearchCursor class (iterator) to get the character's range where is located the DOM element in your editor.
// the import for SearchCursor class
import {SearchCursor} from "#codemirror/search"
// your editor's view
let main_view = new EditorView({ /* your code */ });
// will create a cursor based on the doc content and the DOM element as a string (outerHTML)
let cursor = new SearchCursor(main_view.state.doc, element.outerHTML);
// will search the first match of the string element.outerHTML in the editor view main_view.state.doc
cursor.next()
// display the range where is located your DOM element in your editor
console.log(cursor.value);
2. Highlight that range in the editor.
As described in the migration documentation here, marked text is replace by decoration. To highlight a range in the editor with codemirror 6, you need to create one decoration and apply it in a dispatch on your view. This decoration need to be provide by an extension that you add in the extensions of your editor view.
// the import for the 3 new classes
import {StateEffect, StateField} from "#codemirror/state"
import {Decoration} from "#codemirror/view"
// code mirror effect that you will use to define the effect you want (the decoration)
const highlight_effect = StateEffect.define();
// define a new field that will be attached to your view state as an extension, update will be called at each editor's change
const highlight_extension = StateField.define({
create() { return Decoration.none },
update(value, transaction) {
value = value.map(transaction.changes)
for (let effect of transaction.effects) {
if (effect.is(highlight_effect)) value = value.update({add: effect.value, sort: true})
}
return value
},
provide: f => EditorView.decorations.from(f)
});
// this is your decoration where you can define the change you want : a css class or directly css attributes
const highlight_decoration = Decoration.mark({
// attributes: {style: "background-color: red"}
class: 'red_back'
});
// your editor's view
let main_view = new EditorView({
extensions: [highlight_extension]
});
// this is where the change takes effect by the dispatch. The of method instanciate the effect. You need to put this code where you want the change to take place
main_view.dispatch({
effects: highlight_effect.of([highlight_decoration.range(cursor.value.from, cursor.value.to)])
});
Hope it will help you to implement what you want ;)
Have a look at #codemirror/search.
Specifically, the source code implementation of Selection Matching may be of use for you to adapt.
It uses Decoration.mark over a range of text.
You can use SearchCursor to iterate over ranges that match your pattern (or RegExpCursor)
Use getSearchCursor, something like this:
var cursor = cmEditor.getSearchCursor(keyword , CodeMirror.Pos(cmEditor.firstLine(), 0), {caseFold: true, multiline: true});
if(cursor.find(false)){ //move to that position.
cmEditor.setSelection(cursor.from(), cursor.to());
cmEditor.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
}
Programmatically search and select a keyword
Take a look at getSearchCursor source code it it give some glow about how it works and its usage.
So use getSearchCursor for finding text and optionally use markText for highlighting text because you can mark text with setSelection method of editor.
Selection Marking Demo
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
styleSelectedText: true
});
editor.markText({line: 6, ch: 26}, {line: 6, ch: 42}, {className: "styled-background"});
And it seem this is what you are looking for:
codemirror: search and highlight multipule words without dialog
RegExpCursor is another option that you can use:
new RegExpCursor(
text: Text,
query: string,
options⁠?: {ignoreCase⁠?: boolean},
from⁠?: number = 0,
to⁠?: number = text.length
)
Sample usage at:
Replacing text between dollar signs for Mathml expression.

React-Bootstrap-Typeahead: Capture moment before setting selection into box (to allow conditional rejection)

In React-Boostrap-Typeahead, I need to capture the moment after the mouse has been clicked, but before the menu selection gets loaded into the Typeahead's box.
This is because I need to check items being selected, and for some of them, I need to display an alert with a Yes/No warning. Only if Yes is clicked can I proceed with setting that value into the box. Otherwise I need to reject the selection and keep it whatever it was prior to the mouse click.
I can't use onChange because by that point the selection is already in the box.
I can't use onInputChange because that is for typing rather than for menu selection. I need the post-menu-select, pre-box change.
If there are any workarounds please let me know.
You should be able to achieve what you're after using onChange:
const ref = useRef();
const [selected, setSelected] = useState([]);
return (
<Typeahead
id="example"
onChange={(selections) => {
if (!selections.length || window.confirm('Are you sure?')) {
return setSelected(selections);
}
ref.current.clear();
}}
options={options}
ref={ref}
selected={selected}
/>
);
Working example: https://codesandbox.io/s/objective-colden-w7ko9

Editing an entity's decorated text

We have a Figure decorator which allows us to insert a link which you can hover over to get a preview of an image. We insert this image along with some metadata (caption, etc.) using a modal form. This all works great. However we also want the ability to click the link and pop up the modal to edit it.
Entity.replaceData() works great for updating the metadata, the only problem that remains is the decorated text which comes from the modal too. It appears the Entity knows little to nothing about the content it's decorating.
How can we find and replace the text? Is there a way around this?
(I've tried setting the content in Draft to an arbitrary single character and making the decorator show the content/title (which would be fine), however when trying to delete the figure, Draft seems to jump over the content and delete something before it. I guess it's due to different text lengths. I thought setting it as 'IMMUTABLE' would solve this but that didn't help.)
EDIT:
Here's my decorator:
function LinkedImageDecorator(props: Props) {
Entity.mergeData(props.entityKey, { caption: "hello world" });
const entity = Entity.get(props.entityKey);
const data = entity.getData();
return <LinkedImage data={data} text={props.children} offsetKey={props.offsetKey} />
}
function findLinkedImageEntities(contentBlock: ContentBlock, callback: EntityRangeCallback) {
contentBlock.findEntityRanges((character) => {
const entityKey = character.getEntity();
return (
entityKey != null &&
Entity.get(entityKey).getType() === ENTITY_TYPE.IMAGE
);
}, callback);
}
export default {
strategy: findLinkedImageEntities,
component: LinkedImageDecorator,
editable: false,
};
As you can see, I'm testing out Entity.mergeData which will eventually be the callback of my LinkedImage component (which would open a modal onClick.) So the metadata is easy to update, I just need to be able to update the decorated text which is passed in as props.children.
So I finally solved this with the help of Jiang YD and tobiasandersen. Here goes...
First I inject my decorator with a reference to my editor (which keeps track of EditorState):
const decorators = new CompositeDecorator([{
strategy: findLinkedImageEntities,
component: LinkedImageDecorator,
props: { editor: this }
}];
this.editorState = EditorState.set(this.editorState, { decorator });
From there I can do this in my LinkedImageDecorator:
const { decoratedText, children, offsetKey, entityKey, editor } = this.props;
// This looks messy but seems to work fine
const { startOffset, blockKey } = children['0'].props;
const selectionState = SelectionState.createEmpty(blockKey).merge({
anchorOffset: startOffset,
focusOffset: startOffset + decoratedText.length,
});
const editorState = editor.getEditorState();
let newState = Modifier.replaceText(
editorState.getCurrentContent(),
selectionState,
"my new text",
null,
entityKey,
);
editor.editorState = EditorState.push(editorState, newState, 'insert-fragment');
Not sure if this is the cleanest way of doing this but it seems to work well!
just put the global component instance reference in the decorator props.
const compositeDecorator = new CompositeDecorator([
{
strategy: handleStrategy,
component: HandleSpan,
props: {parent:refToYourComponentWhichContainsTheEditorState}
}
props.parent.state.editorState.getCurrentContent()

How to enable auto focus on SAPUI5 input suggestion field

I'm currently developing a sapui5 mobile application and am using an sap.m.Input with suggestions bound by a model like this:
new Page('page', {
showNavButton: true,
content: [
new sap.m.Input({
id: "input",
type: 'Text',
showSuggestion: true,
suggestionItemSelected: function(event) {
event.getParameters().selectedItem.mProperties.text;
},
liveChange: function() {
// some stuff
}
})
]
});
The Model is created and bound like the following:
var model = new sap.ui.model.json.JSONModel();
// model is filled
sap.ui.getCore().byId('input').setModel(model);
sap.ui.getCore().byId('input').bindAggregation('suggestionItems', '/', new sap.ui.core.Item({
text: "{someField}"
}));
When I now click into the input field on a mobile device, kind of a new screen opens with a new input field, which the user has to manually focus again, what seems like a usability flaw to me.
Is there a nice possibility to enable auto focusing the input field on this new screen, so that the user doesn't has to do it again? Or can this screen be disabled at all on mobile devices?
sap.m.input doesn't seem to have a own method for focusing, or at least I'm not finding one - I already tried using jquery's .focus() on the new input field, but without success.
edit: for clarification, the suggestion works troublefree - only the absence of the auto focus on the appearing new screen is what bothers me.
Here is a workaround to "fix" this behavior: https://jsbin.com/lukozaq
Note 1: The above snippet relies on internal implementation of how the popup works. Use it with caution as there are currently no public APIs to access the corresponding internal controls.
Note 2: I put the word fix in quotes because it seems to be the intended behavior that the user has to click on the second input field explicitly, according to the comment in the source code:
Setting focus to DOM Element, which can open the on screen keyboard on mobile device, doesn't work consistently across devices. Therefore, setting focus to those elements are disabled on mobile devices and the keyboard should be opened by the user explicitly.
That comment is from the module sap.m.Dialog. On a mobile device, when the user clicks on the source input field, a stretched Dialog opens up as a "popup" which has the second input field in its sub header.
Please check the API documentation of sap.m.Input, it has a method focus. You can call:
this.byId("input").focus()
to set the focus into the input field.
Try this:
jQuery.sap.delayedCall(0, this, function() {
this.byId("input").focus()
});
About how to detect when the user presses the input field: maybe something like this? Only problem is that I think you probably don't know the id of the new input field which is shown.
var domId = this.byId("input").getId();
$( "#"+domId ).click(function() {
jQuery.sap.delayedCall(0, this, function() {
this.byId("input").focus()
});
});
I am pretty sure that the first piece of code is how to put focus on an input. I'm not sure about the second part, but it's something to try.
in onAfterRendering() do the below..
onAfterRendering : function() {
$('document').ready(function(){
sap.ui.getCore().byId('input').focus();
});
}
I didn´t have the exactly same problem, but It was similiar.
My problem was that I needed focus into suggestion input when the view had been rendered. My solution was to put following code into "hanldeRouteMatched" function:
var myInput = this.byId("inputName");
var viewName = this.getView().getId();
var selector1 = viewName + "--inputName-inner";
var selector2 = viewName + "--inputName-popup-input-inner";
jQuery.sap.delayedCall(1000, this, function() {
myInput.focus();
if($("#" + selector1)){
$("#" + selector1).click();
jQuery.sap.delayedCall(500, this, function() {
if($("#" + selector2)){
$("#" + selector2).focus();
}
});
}
});
If you see that suggestion input catch focus, but It lose it after, or It never catched it, try increasing the time in delayedCalls. The needed time depends on your connection speed.