I would like to create a custom component in react-markdown. Specifically I need to be able to create a tag that will allow me to create a tooltip. E.g. [name]{ tooltip content}. I found a similar solution in the link. Unfortunately the component is created as a regular div and not as my component. I don't know exactly what I did wrong.
my code:
import React from "react";
import styled from "styled-components";
import ReactMarkdown from "react-markdown";
import directive from "remark-directive";
import { visit } from "unist-util-visit";
import { h } from "hastscript/html.js";
const Test = styled.span`
font-size: 12rem;
font-family: "DM Serif Display", serif;
`;
const TestComp = ({ ...props }) => {
return <Test> {props.children}</Test>;
};
const components = {
myTag: TestComp,
};
// remark plugin to add a custom tag to the AST
function htmlDirectives() {
return transform;
function transform(tree) {
visit(
tree,
["textDirective", "leafDirective", "containerDirective"],
ondirective
);
}
function ondirective(node) {
var data = node.data || (node.data = {});
var hast = h(node.name, node.attributes);
data.hName = hast.tagname;
data.hProperties = hast.properties;
}
}
var markdown =
' Some markdown with a :myTag[custom directive]{title="My custom tag"}';
const Text = () => {
return (
<ReactMarkdown
className={"markdown"}
components={components}
remarkPlugins={[directive, htmlDirectives]}
children={markdown}
linkTarget={"_blank"}
/>
);
};
export default Text;
I use markdown-to-jsx to use custom component.
https://www.npmjs.com/package/markdown-to-jsx
Related
So, I am using react to create an image generator.
With different components (header, sidebar and bannercontent).
In the header I have a button which, when clicked, should generates an image that is located in the bannercontent, which holds a canvas element.
Now I know I can use ref attributes when the elements are in the same component (is easier done), but getting data from the canvas to be generated when the button in the header is clicked, only shows an error:
Cannot read properties of null (reading 'cloneNode')
We use a reducer with dispatch to move data (which is set on the sidebar) to the bannercontent, which works fine.
We use the following code:
This is inside a file called: useBanner.ts
const downloadRef = useRef(null!);
const handleDownloadImage = () => {
const targetEl = downloadRef.current;
DomToImage.toJpeg(targetEl, { quality: 0.95 }).then((dataUrl: any) => {
const link = document.createElement('a');
link.download = 'aff-banner.jpeg';
link.href = dataUrl;
link.click();
});
};
return {
downloadRef,
handleDownloadImage
}
On the bannercontent file we have:
import React from 'react';
import { useBanner } from '../../reducer/useBanner';
const BannerContent = (): React.ReactElement => {
const { downloadRef } = useBanner();
return (
<div ref={downloadRef}>
<canvas ....></canvas>
</div>
);
}
export default BannerContent;
And on the header file:
import { useBanner } from '../../reducer/useBanner';
const Header = (): ReactElement => {
const {
handleDownloadImage
} = useBanner();
return (
<Button onClick={handleDownloadImage}>Download Banner</Button>
);
};
export default Header;
Oh and the structure of those components is:
import Sidebar from '../Sidebar/Sidebar';
import BannerContent from '../BannerContent/BannerContent';
import { BannerProvider } from '../context/BannerContext';
import Header from '../Header/Header';
export default function Content() {
return (
<BannerProvider>
<Header />
<Sidebar />
<BannerContent />
</BannerProvider>
);
}
Let's say I have global.css
.test {
background-color: black;
width: 50px;
height: 50px;
}
For some reason, I need to get the styles data from applied element. I tried refs but it always return empty string.
import { useState, useEffect, useRef } from "react";
const IndexPage = () => {
const divEl = useRef<HTMLDivElement | null>(null);
const [divStyle, setDivStyle] = useState({} as CSSStyleDeclaration);
useEffect(() => {
if (divEl.current) {
setDivStyle(divEl.current.style);
}
}, [divEl.current]);
return (
<div>
<div ref={divEl} className="test"></div>
<pre>{JSON.stringify(divStyle, undefined, 2)}</pre>
</div>
);
};
export default IndexPage;
Is it because next.js SSR or should I add something to dependency array?
code sandbox here
You can use computed styles to get what you need, although it won't be a "simple" object with properties. You'll need to query each property individually:
if (divEl.current) {
setDivStyle(getComputedStyle(divEl.current));
}
and then you can do something like:
divStyle.getPropertyValue("background-color")
Here's an example sandbox (forked from yours) that demostrates it.
import React, { Component } from "react";
import { render } from "react-dom";
import CodeMirror from "react-codemirror";
import "./style.css";
import "codemirror/lib/codemirror.css";
class App extends Component {
constructor() {
super();
this.state = {
name: "CodeMirror",
code: "Hello world Code Mirror"
};
}
updateCode(newCode) {
this.setState({
code: newCode
});
}
render() {
let options = {
lineNumbers: true
};
return (
<div>
<p>Start editing to see some magic happen :)</p>
<CodeMirror
value={this.state.code}
onChange={this.updateCode.bind(this)}
options={options}
/>
</div>
);
}
}
render(<App />, document.getElementById("root"));
I'm working on a tokenizer and want to highlight a specific token from the code.
How to underline or bold a specific token, like world text in this case?
Or is there any other code editor library which can highlight any substring given start and end index?
You can achieve this by using CodeMirror.overlayMode. You need to define your own mode that will parse codemirror's content and set some class to your custom tokens.
Let's say that you define your mode in the customHighlightsMode.js file:
import CodeMirror from 'codemirror';
import 'codemirror/addon/mode/overlay';
CodeMirror.defineMode("customHighlights", function (config, parserConfig) {
var myOverlay = {
token: function (stream) {
if (stream.match(/(world)/)) {
return 'custom-keyword';
} else {
stream.next();
return null;
}
}
};
return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop), myOverlay);
});
Then you need to set a class with styles for your tokens:
.cm-custom-keyword {
font-weight: bold;
color: red;
}
And then you need to set your mode in the CodeMirror options:
import React, { Component } from "react";
import CodeMirror from "react-codemirror";
import "codemirror/lib/codemirror.css";
import "./style.css"; // add .cm-custom-keyword class here
import "./customHighlightsMode";
class App extends Component {
constructor() {
super();
this.state = {
name: "CodeMirror",
code: "Hello world Code Mirror"
};
}
updateCode(newCode) {
this.setState({
code: newCode
});
}
render() {
let options = {
lineNumbers: true,
mode: { name: "customHighlights" },
};
return (
<div>
<p>Start editing to see some magic happen :)</p>
<CodeMirror
value={this.state.code}
onChange={this.updateCode.bind(this)}
options={options}
/>
</div>
);
}
}
Unfortunately, the most documented way of doing this (overlayMode) does not work for CodeMirror 6.
The way to do this in CodeMirror 6 is to create a custom view plugin like this.
import { MatchDecorator, ViewPlugin, Decoration } from "#codemirror/view";
let worldDeco = Decoration.mark({ class: "world" }); // This adds a className to the text that matches the regex.
let decorator = new MatchDecorator({
regexp: /(world)/g,
decoration: (m) => worldDeco,
});
export const customPlugin = ViewPlugin.define(
(view) => ({
decorations: decorator.createDeco(view),
update(u) {
this.decorations = decorator.updateDeco(u, this.decorations);
},
}),
{
decorations: (v) => v.decorations,
}
);
And then you can go ahead and change styles like so
.world {
color: #e06c75;
font-weight: bold;
}
Last step is to hookup the plugin into your Editor view along with other extensions
let view = new EditorView({
extensions: [basicSetup, customPlugin, javascript()],
parent: document.body
})
I am using Draftjs Editor to generate html content. Also using a package draft-js-color-picker that generates inline styles on the Editor content and I am able to extract that to html and save it on the DB. But when I get that same html and convert to the Editor content format it lost all the inline css. How do I render the style inline inside Draftjs Editor?
import { AtomicBlockUtils, Editor, EditorState, RichUtils, convertFromHTML, ContentState } from 'draft-js';
import ColorPicker, { colorPickerPlugin } from 'draft-js-color-picker';
import { stateToHTML } from 'draft-js-export-html';
(...)
class CreateComment extends Component {
(...)
render() {
return(<Editor
blockRendererFn={mediaBlockRenderer}
customStyleFn={this.picker.customStyleFn}
blockStyleFn={this.getBlockStyle}
customStyleMap={styleMap}
editorState={editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
onTab={this.onTab}
ref="editor"
spellCheck={true}
/>)
}
}
although it is an open issue in their Github repo, I found a solution that uses draft-js-import-html library (link)
import { stateFromHTML } from 'draft-js-import-html';
// list of your custom styles
const styleMap: any = {
'ORANGE': {
color: 'rgb(242, 113, 28)'
},
'DARKYELLOW': {
color: 'rgb(226, 180, 1)'
},
'BLACK': {
color: 'rgb(27, 28, 29)'
}
}
const importHtmlOptions = {
customInlineFn: (element, { Style }) => {
if (element.tagName === 'SPAN') {
const color = element.style.color // the element color in RGB
const customStyle = Object.keys(colorStyleMap).find((styleKey: string) => colorStyleMap[styleKey].color === color)
if (customStyle) {
return Style(customStyle)
}
}
}
const content = stateFromHTML(yourHtmlContent, importHtmlOptions);
const newEditState = EditorState.createWithContent(content)
setNewEditorState(newEditState)
Before making it duplicate question please make sure you read my question
I am asking this question in 2019, Where React Js documentation specify we can use SASS in our react project here's link
I want to switch between light theme and dark theme using dynamic variable which is control by user click
My React Code
import React from 'react';
import './App.scss';
class App extends React.Component {
render() {
return (
<div className="Home">
I’m slow and smooth
<button onClick={() => console.log()}>Change theme</button>
</div>
);
}
}
export default App;
My SASS code:
$theme: light; // I want to control this variable
$bgcolor: #222222;
#if($theme== light) {
$bgcolor: white;
}
#else {
$bgcolor: black;
}
.Home {
background-color: $bgcolor;
height: 100vh;
}
in case you are still interested, you can kind of change a sass variable by instead using css variables
:root {
--app-primaryColor: #f49ad1;
--app-secondaryColor: #211f1e;
}
Then use these variables in your scss files
.button {
background-color: var(--app-primaryColor);
color: var(--app-secondaryColor);
}
and update them using React
document.documentElement.style.setProperty('--app-primaryColor', '#ffae00')
Here is a (almost) full example using react and redux. A setTheme action is used to update colors from the document root element. This way you can also configure your theme directly from your react root tag props. These props will be set as the initial state.
// index.html
<div
id="app"
primaryColor="red"
secondaryColor="#f2f2f2"
/>
// css-variables.scss
:root {
--app-primaryColor: #f49ad1;
--app-secondaryColor: #211f1e;
}
// app.module.scss
.button {
background-color: var(--app-primaryColor);
color: var(--app-secondaryColor);
}
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import './css-variables'
import App from './app'
import configureStore from './configureStore'
const rootElement = document.getElementById('app')
//Here you could extract your theme variables from your rootElement props and set it as an initial redux state
const initialProps = {}
const store = configureStore(initialProps)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement)
// app.js
import React from 'react'
import { connect } from 'react-redux'
import { setTheme } from './actions'
import styles from './app.module'
class App extends React.Component {
componentDidMount() {
//In case you have an initial state set from your rootElement
const { theme, setTheme } = this.props
setTheme(theme)
}
generateTheme() {
const primaryColors = ['#ffae00', '#f49ad1', '#d0666b', '#7c6cd0', '#6cd09d', '#d0ba6c']
const secondaryColors = ['#4c4c4e', '#2f2f2f', '#dcdcdc', '#fff']
return {
primaryColor: primaryColors[Math.floor(Math.random() * primaryColors.length)]
secondaryColor: secondaryColors[Math.floor(Math.random() * secondaryColors.length)]
}
}
onButtonClick() {
const theme = this.generateTheme()
this.props.setTheme(theme)
}
render() {
return (
<div className="{styles.button}" onClick={this.onButtonClick.bind(this)}>
Change theme
</div>
)
}
}
const mapStateToProps = (state) => ({
theme: state.theme,
})
export default connect(mapStateToProps, { setTheme })(App)
// actions.js
export const setTheme = theme => dispatch => {
//You change your theme vars directly from the root element
Object.keys(theme)
.filter(prop => typeof theme[prop] !== 'undefined' && theme[prop] !== null)
.forEach(prop => document.documentElement.style.setProperty(`--app-${prop}`, theme[prop]))
dispatch({
type: 'THEME/SET',
payload: theme
})
}