How to make separate each element - javascript

How can I make each of this element separated. Currently, each elements is merged into one <span> element
<span style="font-family:wingdings;">.4=??????</span>
<span style="font-family:symbol;">QPGH</span>
this is generated by code below
import Command from '#ckeditor/ckeditor5-core/src/command';
export default class SymbolsCommand extends Command {
execute({ charButtons }) {
const { model } = this.editor;
model.change((writer) => {
const { selection } = model.document;
const position = writer.createPositionAt(selection.getFirstPosition());
console.log(charButtons, 'charButtons');
const renderChars = () => charButtons.map(({ fontFamily, char }) => {
writer.insertText(char, { fontFamily }, position);
});
return renderChars();
});
}
refresh() {
this.isEnabled = true;
}
}
I expect the output like
<span style="font-family:wingdings;">=</span>
<span style="font-family:wingdings;">?</span>
<span style="font-family:wingdings;">4</span>
<span style="font-family:symbol;">4</span>
<span style="font-family:symbol;">4</span>
...

Joining similar <span>s is a default CKEditor 5 behavior. One of the reasons is that those characters are also joined in the data model and represented by a one text node. It doesn't matter if you insert those characters one by one or all at once, if they have the same attributes they are grouped together.
One of the ways to prevent that from happening is to specify view.AttributeElement#id. This is unfortunately a more advanced subject. Among other, you will have to provide converters that will create attribute elements in the view.
I think there are two ways to achieve your goal, both will require you to add a converter instead of relying on fontFamily converter from font family plugin (I assume this happens here).
Using attribute on text with unique AttributeElement#id to prevent joining <span>s
The first solution is to introduce a new attribute for text (remember about extending schema) and provide converter for it. Let's call the attribute key symbol.
The converter would have to convert given text node character-by-character and set unique id for each created attribute span.
This is a more complicated solution, although probably a better one.
editor.model.conversion.for( 'downcast' ).add( dispatcher => {
dispatcher.on( 'attribute:symbol', ( evt, data, conversionApi ) => {
// Provide your converter here. It should take `data.item`, iterate
// through it's text content (`data.item.data`), use
// `conversionApi.writer` to create attribute elements with unique ids
// and use the writer and `conversionApi.mapper` to place them in the view.
} );
} );
You could base the converter on function wrap from downcasthelpers.js in the engine: https://github.com/ckeditor/ckeditor5-engine/blob/master/src/conversion/downcasthelpers.js.
Insert symbols as inline elements
Another solution would be to insert elements with characters instead of simply text nodes.
In this case, again, you have to specify the new model element in the schema. Maybe extending the '$text' item.
For conversion, you could probably use elementToElement helpers from editor.conversion.for(). For downcast you would have to specify view as a callback and set unique id there (unique ids could be simply a counter, incremented by one each time). If elementToElement won't work for downcasting (it should work for upcast) you will need to provide a custom converter through .for( 'downcast' ).add( ... ).
This solution is easier but I am not sure it will work. It's hard to say which one is better because it depends also on what exactly you want to achieve. I'd probably try both but I'd focus on trying to do it using the first approach.
I wish there was an easier way to achieve this at the moment but this use case is quite rare, so the architecture was focused in another direction.

Related

how to select element with getByTestId that also has specific value with react-testing-library

In react, I am mapping out some elements from an array. For example
{options.map((option)=>{
return <div data-testid="option">{option}</div>
})
I have multiple tests where I want to select an option (without knowing what the textContent of the option is) so I used data-testid="option" and select with screenGetAllByTestId('option')[0] to select the first option...
However, there are some times that I know what particular option I want, and I want to getByTestId but because they all share the same data-testid its a bit harder.
What I'm trying to do, is something similar to this pseudo code:
screen.getAllByTestId('option').getByText("Apples")
which will get all the options, but then get the specific one that has Apples as text
You can write custom matcher functions too, like:
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})
From the docs
I think in your case, that translates to:
screen.getByText((content, element) => {
return element.getAttribute('data-testid').toLowerCase() === 'option' && content === 'Apples'
})
An even better solution might be to have legible text labels attached to each group of options, so your tests could say:
// Find your menu / list header
const fruitsListHeader = screen.getByText('Fruits list');
// Find the menu or whatever it's in
// You can use plain old query selectors
const fruitsList = fruitsListHeader.closest('[role="menu"]')
// Look for your option in the right place, instead of
// praying there are no duplicated options on the page.
const appleOption = within(fruitsList.closest('[role="menu"))
.getByText('Apples');
within docs

standard way of adding html data attribute in html tags?

I am adding an HTML data attribute, but I am confused about what the standard convestion is regarding the data attribute name. I am understand single-word data attributes (data-name="" or data-id="") etc, but what is the convention if I use more than one word in data attribute name? Do I separate the two words, (data-product_id="" or data-product-id=""), or some form of camel-casing like: data-productID=""?
Data attributes should probably use hyphen-separated words, like data-product-id. This allows them to be automatically translated to camelCase when accessing them in Javascript via dataset (as most Javascript variables are generally expected to be in camelCase):
const div = document.querySelector('div');
console.log(div.dataset.productId);
<div data-product-id=5></div>
Using uppercase to indicate word separation isn't a great idea, because it'll be lost when checking the dataset, which will significantly harm readability with multiple words when the attribute is long:
const div = document.querySelector('div');
console.log(div.dataset);
<div data-productID=5></div>
Using an underscore in the data attribute would result in the dataset property having an underscore too, which is a bit odd, since you'll probably have to translate it to camelCase afterwards to keep in line with variable naming conventions:
const [div1, div2] = document.querySelectorAll('div');
(() => {
// a bit verbose:
const productId = div1.dataset.product_id;
console.log(productId);
})();
(() => {
// more consise and consistent
// when the variable name is the same as the property name:
const { productId } = div2.dataset;
console.log(productId);
})();
<div data-product_id=5></div>
<div data-product-id=6></div>

Transient formatting in a Quill document?

I'd like to implement transient highlighting in a Quill document.
For example, imagine a SEARCH button where the user can highlight all instances of a keyword in the current document, by setting the text color for matching text ranges.
I can do that today, with something like this:
var keyword = "hello";
var text = quill.getText();
var matchIndex = text.indexOf(keyword);
while (matchIndex >= 0) {
quill.formatText(matchIndex, keyword.length, { "color" : "#f00" });
matchIndex = text.indexOf(keyword, matchIndex + keyword.length);
}
But I don't want the resultant deltas to be incorporated into the official change history of this document. These are just transient highlights, and I'd like to be able to clear them all away with something like this...
quill.clearTransientFormats();
I'd even like to give the user the choice of leaving the transient highlights enabled, while they continue to edit and modify the document with their own destructive changes.
Essentially, I want to have two different kinds of formatting:
Destructive formatting is always recorded in the sequence of deltas persisted in the document history.
Transient formatting is ignored in the document history, since it only applies to the current view.
What's the best way to implement something like this?
I would recommend just post-processing the delta before saving. It can be achieved fairly easily with compose:
var length = aboutToBeStored.length();
var toStore = aboutToBeStored.compose(new Delta().retain(0, length, { color: null }));

Javascript: get content of an element with incorrect style definition

I'm creating a stylesheet for a certain site, using javascript to assign classes to certain elements. For whatever reason some of 'td' elements use weird class assignments and inline styles, so I loop over them, clean them up and assign proper class names to reference in external stylesheet.
I don't have access to the site itself (nor administrative permission to change anything) so I use Stylish and Greasemonkey for Firefox (plus Adblock to dispose of the original stylesheet).
Here is a little part of js code responsible for that:
var cellStyleBg = cCell.style.backgroundColor;
if (cellStyleBg) {
switch(cellStyleBg) {
case 'white':
cCell.removeAttribute('style');
if ( cCell.parentNode.nodeName == 'TR' ) {
cCell.parentNode.className = 'deadlineSet';
}
break;
...
The problem is there is one particular case where this doesn't work:
<td class="td2h" style="background: dd97f0;">
As you can see, there is no octotorp in front of the color code. I assume that is the reason in this one case the variable cellStyleBg is 'null'.
I tried (instead of 'cCell.style.backgroundColor') to use 'cCell.style' and 'cCell.style.background', but they don't return plain text for me to parse.
What can I do to handle this particular case?
I think the only way to go is to get the raw value of the style attribute.
You do this by
//will output background: dd97f0
cCell.getAttribute('style');
If needed, you can breakdown the style into key value pairs using this snippet
//give "background: dd97f0", will output "background=dd97f0"
var stylePattern = /(.*?):([^;]*);?/g
while (match = stylePattern.exec(style)) {
console.log(match[1].trim() + "=" + match[2].trim());
}

Insert innerHTML with Prototypejs

Say I have a list like this:
<ul id='dom_a'>
<li>foo</li>
</ul>
I know how to insert elements in the ul tag with:
Element.insert('dom_a', {bottom:"<li>bar</li>"});
Since the string I receive contains the dom id, I need to insert the inner HTML instead of the whole element. I need a function to do this:
insert_content('dom_a', {bottom:"<ul id='dom_a'><li>bar</li></ul>"});
And obtain:
<ul id='dom_a'>
<li>foo</li>
<li>bar</li>
</ul>
How should I do this with Prototype ?
Here is the solution I have come up with, can anyone make this better ?
Zena.insert_inner = function(dom, position, content) {
dom = $(dom);
position = position.toLowerCase();
content = Object.toHTML(content);
var elem = new Element('div');
elem.innerHTML = content; // strip scripts ?
elem = elem.down();
var insertions = {};
$A(elem.childElements()).each(function(e) {
insertions[position] = e;
dom.insert(insertions);
});
}
I think you could parse the code block in your variable, then ask it for its innerHTML, and then use insert to stick that at the bottom of the actual node in the DOM.
That might look like this:
var rep_struct = "<ul id='dom_a'><li>bar</li></ul>";
var dummy_node = new Element('div'); // So we can easily access the structure
dummy_node.update(rep_struct);
$('dom_a').insert({bottom: dummy_node.childNodes[0].innerHTML});
I think you can slim down the code a bit by simply appending the innerHTML of the first child of temporary element:
Zena.insert_inner = function(dom, position, content) {
var d = document.createElement('div');
d.innerHTML = content;
var insertions = {};
insertions[position] = d.firstChild.innerHTML;
Element.insert(dom, insertions);
}
Not too much of an improvement though, example here.
I've been looking into the Prototype Documentation and I found this: update function.
By the way you described it, you could use the update function in order to find the current bottom content and then update it (just like innerHTML) by adding the desired code plus the previous stored code.
You could use regular expression to strip the outer element.
Element.Methods.insert_content = function(element, insertions) {
var regex = /^<(\w+)[^>]*>(.*)<\/\1>/;
for (key in insertions) {
insertions[key] = regex.exec(insertions[key])[2];
}
Element.insert(element, insertions);
};
Element.addMethods();
$('dom_a').insert_content({bottom:"<ul id='dom_a'><li>bar</li></ul>"});
If you are using PrototypeJS, you might also want to add script.aculo.us to your project. Builder in script.aculo.us provides a nice way to build complex DOM structures like so:
var myList = Builder.node("ul", {
id: "dom_a"
},[
Builder.node("li", "foo"),
Builder.node("li", "bar"),
]);
After this, you can insert this object which should be rendered as HTML anywhere in the DOM with any insert/update functions (of PrototypeJS) or even standard JavaScript appendChild.
$("my_div").insert({After: myList});
Note that in PrototypeJS insert comes in 4 different modes: After, Before, Top and Bottom. If you use insert without specifying a "mode" as above, the default will be Bottom. That is, the new DOM code will be appended below existing contents of the container element as innerHTML. Top will do the same thing but add it on top of the existing contents. Before and After are also cool ways to append to the DOM. If you use these, the content will be added in the DOM structure before and after the container element, not inside as innerHTML.
With Builder however, there is one thing to keep in mind, .. okay two things really:
i. You cannot enter raw HTML in the object as content... This will fail:
Builder.node("ul", "<li>foo</li>");
ii. When you specify node attributes, keep in mind that you must use className to signify HTML attribute class (and possibly also htmlFor for for attribute... although for attribute seems to be deprecated in HTML5(?), but who does not want to use it for labels)
Builder.node("ul", {
id: "dom_a",
className: "classy_list"
});
I know you are scratching your head because of point i. > What, no raw HTML, dang!
Not to worry. If you still need to add content which might contain HTML inside a Builder created DOM, just do it in the second stage using the insert({Before/After/Top/Bottom: string}). But why'd you want to do it in the first place? It would be really good practice if you wrote an once for all function that generates all kinds of DOM elements rather than stitching in all sorts of strings. The former approach would be neat and elegant. This is something like the inline style versus class type of question. Good design should after all separate content from meta content, or formatting markup / markdown.
One last thing to keep handy in your toolbox is Protype's DOM traversal in case you want to dynamically insert and delete content like a HTML Houdini. Check out the Element next, up, down, previous methods. Besides the $$ is also kinda fun to use, particularly if you know CSS3 selectors.

Categories