checking how many lines catches some text - html

How could I check how many lines a rendered text takes, no matter if it's displayed on a large or small screen?
On the below component I want to show the anchor element only if the text has 3 lines.
import React from 'react';
type contentProps = {
content: string,
};
function subString(string: string): string {
return string.substring(0, string.length / 2);
}
export const Content = ({ content }: contentProps) =>
content ? (
<p className="content">
{subString(content)}
See More
</p>
) : null;
What should be the flow ?
First i need to render the component
Second check if it takes 3 lines and after this change the state ?

Related

How to concatenate a string inside the react-draft-wysiwyg Editor and display it in the text area dynamically?

Basically I have a mini forum where there is a list of comments. My goal is to make it so that once a user clicks to quote a comment button, that comment goes into the text box, where the user can then write below the quote.
Something like we had on Orkut:
Right now my site looks like this, when I click on quotar(quote) I must put the comment text "Just a test" (for example) and put it in the react-draft-wysiwyg Editor.
OBS: I can already get the string from the text comment when I click on Quote, that's not the problem, I just need a way to put this string inside the Editor.
This is my Editor from react-draft-wysiwyg:
const EditorComponent = ({ setNewCommentContent}: Props) => {
const [editorState, setEditorState] = useState<EditorState>(EditorState.createEmpty());
return (
<>
<Editor
placeholder="New Comment"
editorState={editorState}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={(newState) => {
setEditorState(newState);
setNewCommentContent(draftToHtml(convertToRaw(newState.getCurrentContent())));
}}
toolbar={{
...
}}
/>
</>
);
};
On my comments page, I'm using setContent to try to put the quoted comment inside the Editor content, and I actually manage to do that, but the content isn't shown in the textbox even though it's in the content inside of the Editor.
const handleQuote = (commentId: number, e: any) => {
e.preventDefault();
const commentIdSearch = "comment_" + String(commentId);
//Here I get the comment html
const comment_text_div: HTMLCollection | undefined = document.getElementById(commentIdSearch)?.children;
if (comment_text_div) {
setHideTextArea(false);
//from the html I extract the text
const quoteContent: string = handleContent(comment_text_div);
/**
* Here I place the quote inside the text area of the new comment, but it is not enough
*to show it in the text box of the Editor. I know it's actually inside the content, because if
*I submit the new comment I can see the result, and the comment quote text is there.
*/
setNewCommentContent((prevtState) => prevtState + newCommentContent);
}
};
Can someone help me with these please? I'm almost giving up using react-draft-wysiwyg to create my own custom textbox, or at least trying it.

How to show a random element of an json object from localhost?

I wish to show a random quote from a local database every time I reload the page. I'm fetching the data from localhost:3000 using custom useFetch hook but all I've ever did was map trough the entire object and show every element; now I only need to show one element and make it random. This is my attempt:
const Header = () => {
const { data } = useFetch('http://localhost:3000/Quotes')
const quotes = data.map(quote => ({ quote }))
const quote = quotes[Math.floor(Math.random() * quotes.length)]
return (
<div className='header'>
<div className='quothe'>
<h2 key={quote.id}>{quote.title}</h2>
<h3 key={quote.id}>- {quote.author}</h3>
</div>
</div>
)
}

How is data handled with getStaticProps when component uses it in useEffect()?

I have a page that uses the getStaticProps function. The function loads a list of objects that each hold three things: the title of a book, the author of a book, and then a list of characters in the book. The page then maps each of these objects to a component that puts them in a pretty pill.
The component takes the title and author and embeds it into its HTML code. However, the one complexity is the component also uses a useEffect() hook to randomly select 1 character within the character list provided as a prop and then displays them as part of the component HTML. Since it is useEffect(), this does not happen at build time. The reason I want it to occur when the user requests the page is that each user should see a different randomly selected character (i.e., although everything else on the page is the same for all users, I want the character to be randomly selected).
My question is, can getStaticProps work here? Does it build out the HTML as best it can and then when the user requests the page, the character list data is already provided? Or because it uses useEffect(), the component will have to re-request the data from the backend? I want to limit backend requests because the data is stored in AirTable and that only allows 5 calls per second.
index.jsx:
const Home = (props) => {
return (
<div className="flex min-h-screen">
<main className="relative mx-5 mt-16">
{props.response.map((bookData) => (
<BookPill
bookTitle={bookData['Book Title']}
bookAuthor={bookData['Book Author']}
bookCharacters={bookData['Characters']}
/>
))}
</main>
</div>
)
}
export default Home
export async function getStaticProps(context) {
try {
const response = await getBookData()
return {
props: {
response,
},
revalidate: 5,
}
} catch (error) {
return {
props: {
err: 'Something went wrong 😕',
},
}
}
}
BookPill.jsx:
const BookPill = ({
bookTitle,
bookAuthor,
bookCharacters,
}: PropsType) => {
const [randomCharacter, setRandomCharacter] = useState('')
useEffect(() => {
const random_character =
bookCharacters[Math.floor(Math.random() * bookCharacters.length)]
setRandomCharacter(random_character)
}, [])
return (
<div className="my-2 grid grid-cols-1">
<div className="px-5 text-center">
<p className="mb-4">{bookTitle}</p>
<p className="text-sm">{bookAuthor}</p>
</div>
<div className="flex items-center justify-center md:col-span-1">
<div className="rounded-3xl">
{randomCharacter}
</div>
</div>
</div>
)
}
export default BookPill
To summarize what was discussed in the comments:
Because you're using getStaticProps with revalidate: 5, the data will only be fetched on the server, at most every 5 seconds. The data fetched inside getStaticProps is then passed to the HTML generated for the page - you can see the props returned from getStaticProps in the __NEXT_DATA__ script in your HTML.
When the page gets rendered on the browser, the useEffect is triggered and will use that data. No additional requests occur on the client-side.

Dynamically add elements to the editable div with Angular and ANYWHERE

So, I am creating an HTML interface where user should be able to write a text and push it as a notification to our mobile app.
I am facing some troubleshoots with the text and the dynamic inserted elements using Angular 5;
The text can contain special elements like: phone number, Location and website URL. Those special elements will be inserted by pressing on a button that opens a dialog, and for each one its specific fields are displayed, like google maps for location and input fields for Web URL and mobile Phone. It is implemented this way in order to capture longitude, latitude and phone numbers on save button in order to add them as buttons to the received push on the devices.
Anyway, the above is implemented and could work successfully except the way of adding dynamically spans of special elements inside the div of the web interface. Spans added must have a class and a click event to display again the dialog in order to modify the data. Also they can be inserted anywhere inside the big div depending on user's choice.
Below is the image of above description.
The blue spans, are the ones that should be added dynamically inside the content editable div that can be filled by around 450 characters.
So how to solve the issue and enable the feature of adding clickable and designed spans with icons inside a content editable div, and be able in a final stage to retrieve data?
My code is the below, working but for a specific/predefined position:
Message.html
<div id="myMessage" contenteditable="true" dir="ltr" [innerHTML]="contentEN | safeHtml"
style=" height: 80px;border: 1px solid #c1c1c1; padding: 7px;">
</div>
<ng-container #vc>
</ng-container>
Message.ts
#ViewChild('vc', {read: ViewContainerRef}) target: ViewContainerRef;
createSpanPhone(spanIDNumber, phoneDescription, phoneValue ){
// here the span Phone is created dynamically outside the div
let phoneComponent = this.cfr.resolveComponentFactory(PhoneComponent);
this.componentRef = this.target.createComponent(phoneComponent);
}
PhoneComponent.ts
import { Component } from '#angular/core';
import { faPhone } from '#fortawesome/free-solid-svg-icons';
#Component({
selector: 'my-phone',
template: '<span contenteditable="false" (click) = "test()" class="BAN_Tags_IN_Text"> <fa-icon
[icon]="faPhone" class="faSpanIcon"> </fa-icon> <span class="phoneDesc"
data-attr="EN">hello</span> <span class="phoneVal" ><b>12346</b></span>
</span>'
})
export class PhoneComponent {
faPhone = faPhone; // trying the icon
constructor(){
}
test(){
console.log("Hiiii"); // trying the click event
}
}
The ViewContainerRef is filled successfully but I need to fill spans in the div above (id=myMessage) and not in a predefined position.
if your text are simple text (don't has html tags that can not enclosed by <span>, -I want to mean that is allowed e.g. <i> or <b>, but not <p> - you can create a component like
#Component({
selector: "html-content",
template: `
<span class="inline" [innerHTML]="value"></span>
`
})
export class HtmlComponent {
#Input() value;
constructor() {}
}
A directive like
#Directive({ selector: "[content]" })
export class ContentDirective {
#Input() set content(textHtml: string) {
this.viewContainerRef.clear();
if (!textHtml) return
//If not end with . or space, add an space
if (textHtml.slice(-1)!=" " && textHtml.slice(-1)!=".")
textHtml+=" "
//gets the "words"
//const parts = textHtml.match(/\ ?\S+\ |\ ?\S+\./gi);
const parts = textHtml.match(/<?[^\r\n\t\f\v< ]+\ ?/gi);
parts.forEach(h => {
let space = false;
let search = h.replace(/[\ .;,:]/gi, "")
let arg=null;
//to allow pass arguments to the components in the way, e.g.
// <phone=arguments -be carefull! the arguments can not contains spaces
//
if (search.match(/<phone=.+/))
{
arg=search.split("=")[1].split(">")[0]
search="<phone>"
}
if (search.match(/<location=.+/))
{
arg=search.split("=")[1].split(">")[0]
search="<location>"
}
switch (search) {
case "<phone>":
case "<location>":
const factory =
search == "<phone>"
? this.componentFactoryResolver.resolveComponentFactory(
PhoneComponent
)
: this.componentFactoryResolver.resolveComponentFactory(
LocationComponent
);
const phone=this.viewContainerRef.createComponent(factory);
//if our component has "#Input() arg"
(phone.instance as any).arg=arg||"";
break;
default:
const factoryHtml = this.componentFactoryResolver.resolveComponentFactory(
HtmlComponent
);
const html = this.viewContainerRef.createComponent(factoryHtml);
html.instance.value = h;
space = true;
break;
}
//this allow write space or dot after the component.
if (!space && h.match(/.+>[\ ;,:.]/gi)) {
const factoryDot = this.componentFactoryResolver.resolveComponentFactory(
HtmlComponent
);
const html = this.viewContainerRef.createComponent(factoryDot);
//we check if, after the component we has a "," or ";" or ":" or ". "
html.instance.value = h.slice(h.indexOf(">")+1)
}
});
//just for check the parts
console.log(textHtml, parts);
}
constructor(
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver
) {}
}
You can see a stackblitz without warranty

Get cursor position in a contenteditable div using innerHTML and pipes

I'm writing a simple WYSIWYG editor in Angular 5 to handle tags in the text. Those tags are like variables. For instance when doing: Hi (!--username--), welcome! it's rendered as Hi alex, welcome!. In order to be user-friendly for the non-technical, the WYSIWYG is transforming (!--username--) to a pretty HTML fragment showing directly "Alexandre" in its content.
This editor needs to handle simple HTML tags too (<b>, <i>, ...)
To do that, I've developed a component named editor which is using Angular's value accessors and showing a simple div like that:
<div class="editor" #editor [innerHTML]="content | prettytags: completions" (focus)="toogleToolbar()" (focusout)="toogleToolbar()"
(click)="onClick($event)" (keyup)="onKey($event)" [attr.contenteditable]="!readonly"></div>
The pipe looks like (for information, completions is the variable containing all tags values):
const pattern: RegExp = /(\(!--[^\s-]*--\))/;
#Pipe({
name: 'prettytags'
})
export class PrettyTagsPipe {
constructor(private sanitizer: DomSanitizer) {}
transform(value: string, completions: any[]): SafeHtml {
if (isNil(value)) return '';
const text = this.makeText(value, completions, 0);
return this.sanitizer.bypassSecurityTrustHtml(text);
}
private makeText(value: string, completions: any[], index: number): any {
const text = value
.split(pattern)
.map(word => {
const tag = completions.find(t => t.tag === word);
return isNil(tag)
? word
: this.getTagHtml(tag.value)
})
.join('');
return text;
}
private getTagHtml(text: any) {
return `<span class="chip" spellcheck="false">${text}</span> `;
}
}
In order to get the two-way data binding working as I'm using [innerHTML], I'm using the keyup event to get new characters but I need to get the caret position to append new characters. To do that I've copy/pasted a function found on Stack Overflow to get the caret position:
private getCaretPosition() {
const element = document.querySelector('.editor');
const range = window.getSelection().getRangeAt(0);
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
return preCaretRange.toString().length;
}
And on my onKeyUp: I do the following:
[...]
const position = this.getCaretPosition();
this.content += key.length === 1 ? this.content.slice(0, position) + key + this.content.slice(position) : '';
but it's not working as it gets the text position.
For instance, if the user wants to edit the content: from Hi (!--username--), welcome! to Hi (!--username--), I'm fine to see you back!, he will place his caret just after the comma, so I'll get 8 (for "Hi alex,") but with my content variable I'll get Hi (!--u.
I know I can get the position of the cursor with HTML tags, but I'll need to do many computations for each key pressed.
Do you have any idea to get this thing to work?