How to change specific token style in code mirror editor? - reactjs

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
})

Related

Modify my SPFx web part to reference css file instead of writing the css code inside the web part

I have built a web part that is similar to this web part from GitHub # https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-enhanced-list-formatting. The web part allows embedding custom CSS code inside SharePoint modern pages. But is it possible to modify the web part, to reference a CSS file instead of having to write the actual CSS code inside the web part? So instead of writing the CSS code as follow:-
to reference the css file as follow:-
Here is the EnhancedListFormattingWebPart.ts:-
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '#microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
} from '#microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '#microsoft/sp-webpart-base';
import * as strings from 'EnhancedListFormattingWebPartStrings';
import EnhancedListFormatting from './components/EnhancedListFormatting';
import { IEnhancedListFormattingProps } from './components/IEnhancedListFormattingProps';
import { PropertyPaneWebPartInformation } from '#pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
import { PropertyFieldMonacoEditor } from '../../controls/PropertyFieldMonacoEditor';
export interface IEnhancedListFormattingWebPartProps {
css: string;
acceptedDisclaimer?: boolean;
}
export default class EnhancedListFormattingWebPart extends BaseClientSideWebPart <IEnhancedListFormattingWebPartProps> {
public render(): void {
const element: React.ReactElement<IEnhancedListFormattingProps> = React.createElement(
EnhancedListFormatting,
{
css: this.properties.css,
acceptedDisclaimer: this.properties.acceptedDisclaimer,
displayMode: this.displayMode,
context: this.context,
onAcceptDisclaimer: ()=>this._handleAcceptDisclaimer()
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
// protected get dataVersion(): Version {
// return Version.parse('1.0');
// }
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyFieldMonacoEditor('css', {
label: strings.CSSFieldLabel,
key: "cssText",
value: this.properties.css,
defaultValue: this.properties.css,
language: "css",
theme: "vs-light",
showLineNumbers: false,
onPropertyChange: (_propertyPath: string, _oldValue: string, value: string) => this._handleSave(value),
}),
PropertyPaneWebPartInformation({
description: strings.CSSDisclaimer,
key: 'cssDisclaimer'
})
]
}
]
}
]
};
}
private _handleAcceptDisclaimer = () => {
this.properties.acceptedDisclaimer = true;
this.render();
}
private _handleSave = (value: string) => {
this.properties.css = value;
}
}
here is the EnhancedListFormatting.tsx:-
import * as React from 'react';
import styles from './EnhancedListFormatting.module.scss';
import * as strings from 'EnhancedListFormattingWebPartStrings';
import { IEnhancedListFormattingProps } from './IEnhancedListFormattingProps';
import { MessageBarButton, MessageBar, MessageBarType } from 'office-ui-fabric-react';
import { DisplayMode } from '#microsoft/sp-core-library';
export default class EnhancedListFormatting extends React.Component<IEnhancedListFormattingProps, {}> {
public render(): React.ReactElement<IEnhancedListFormattingProps> {
const { css, displayMode, acceptedDisclaimer } = this.props;
// If we accepted the disclaimer, let's work as expected
// Determine if there is a CSS value
const hasCSS: boolean = css !== undefined && css !== "";
// Create a style element
const styleElem: JSX.Element = <style type="text/css">{css}</style>;
// If we're not in Edit mode, hide this web part
if (displayMode !== DisplayMode.Edit) {
return styleElem;
}
// if we didn't accept the disclaimer, show a disclaimer and nothing else
if (acceptedDisclaimer !== true) {
return (<MessageBar
onDismiss={()=>this._onAcceptDisclaimer()}
dismissButtonAriaLabel={strings.DismissDisclaimerAriaLabel}
messageBarType={MessageBarType.warning}
actions={
<div>
<MessageBarButton onClick={()=>this._onAcceptDisclaimer()}>{strings.AcceptDisclaimerButton}</MessageBarButton>
</div>
}
>
<div className={styles.disclaimerText} dangerouslySetInnerHTML={{__html: strings.DisclaimerText}}></div>
</MessageBar>);
}
return (
<div className={styles.enhancedListFormatting}>
{styleElem}
<MessageBar
messageBarType={hasCSS ? MessageBarType.success : null}
isMultiline={false}
actions={
<div>
<MessageBarButton onClick={() => this._onConfigure()}>{hasCSS ? strings.PlaceholderButtonTitleHasStyles : strings.PlaceholderButtonTitleNoStyles}</MessageBarButton>
</div>
}
>
{hasCSS ? strings.PlaceholderDescriptionHasStyles : strings.PlaceholderDescriptionNoStyles}
</MessageBar>
</div>
);
}
private _onAcceptDisclaimer() {
this.props.onAcceptDisclaimer();
}
private _onConfigure() {
this.props.context.propertyPane.open();
}
}
any idea, how i can achieve this? I want to do so , as we are planing to create a lot of pages which have the custom css. And instead of having to modify all those pages in the future to change their css, we will only need to change the related css file, and the change will be applied automatically to all the pages at once.
Thanks
There is built-in support for theming in SharePoint. It includes both custom CSS support, custom color schema support, etc. You can start over here:
https://learn.microsoft.com/en-us/sharepoint/dev/general-development/themes-overview-for-sharepoint
The idea of modern web parts and removal of the "script editor" is to prohibit users from adding links to some random sources, so only secure approved things are allowed.

How to use leaflet-polylinedecorator in react 16.4.1

I'm trying to use the leaflet plugin polylinedecorator in react 16.4.1 (so without hooks). However, the only example I have been able to find on how to use this plugin in react is using hooks (see: How to use polylinedacorator with react leaflet), and I am unsure how I can adapt this to be able to use it in my code.
What I have so far is this polylinedecorator component:
import React, { Component } from "react";
import { Polyline } from "react-leaflet";
import L from "leaflet";
import "leaflet-polylinedecorator";
export default class PolylineDecorator extends Component {
componentDidUpdate() {
if (this.props.map) {
const polyline = L.polyline(this.props.positions).addTo(this.props.map);
L.polylineDecorator(polyline, {
patterns: [
{
offset: "100%",
repeat: 0,
symbol: L.Symbol.arrowHead({
pixelSize: 15,
polygon: false,
pathOptions: { stroke: true }
})
}
]
}).addTo(this.props.map);
}
}
render() {
return <></>;
}
}
That I am using like this:
import React, { Component } from "react";
import { Polyline, LayerGroup } from "react-leaflet";
import PolylineDecorator from "./PolylineDecorator";
export default class RouteLayer extends Component {
render() {
const { beginLocations } = this.props;
const locations = [];
const differentLocations: [];
beginLocations.forEach((location, index) => {
// some filtering going on here and pushing the locations to one of the
two arrays (locations, differentLocations)
});
return (
<LayerGroup>
<PolylineDecorator
map={this.props.map}
positions={locations}
color="#4e5c8d"
interactive={false}
/>
<PolylineDecorator
map={this.props.map}
positions={differentFloorLinesLocations}
color="red"
interactive={false}
/>
</LayerGroup>
);
}
}
The RouteLayer is nested inside the map as follows (for simplicity some components are left out):
<LeafletMap
ref={r => {
this.map = r;
if (this.props.setRefMap) {
this.props.setRefMap(r);
}
}}>
<RouteLayer
map={this.map ? this.map.leafletElement : null}
locations={locations}
/>
</LeafletMap>
Right now the polylines are drawn, however not quite as expected since the filtering doesn't seem to work (this filtering worked fine when I was just using polylines without the decorator).
The arrows I am trying to decorate the lines with are showing up, so that's good. However, I'm not happy with how the PolylineDecorator class is looking right now, this doesn't seem like the correct way to do this.
I'm also unsure if it is correct to pass the reference to the map in the way that I'm doing here.
Any suggestions on how to make this work correctly are appreciated.
For React version < 16.8 the following component demonstrates how to integrate L.polylineDecorator with React-Leaflet:
import React, { Component } from "react";
import { Polyline, withLeaflet } from "react-leaflet";
import L from "leaflet";
import "leaflet-polylinedecorator";
class PolylineDecorator extends Component {
constructor(props) {
super(props);
this.polyRef = React.createRef();
}
componentDidMount() {
const polyline = this.polyRef.current.leafletElement; //get native Leaflet polyline
const { map } = this.polyRef.current.props.leaflet; //get native Leaflet map
L.polylineDecorator(polyline, {
patterns: this.props.patterns
}).addTo(map);
}
render() {
return <Polyline ref={this.polyRef} {...this.props} />;
}
}
export default withLeaflet(PolylineDecorator);
Usage
export default class MyMap extends Component {
render() {
const { center, zoom } = this.props;
const polyline = [[57, -19], [60, -12]];
const arrow = [
{
offset: "100%",
repeat: 0,
symbol: L.Symbol.arrowHead({
pixelSize: 15,
polygon: false,
pathOptions: { stroke: true }
})
}
];
return (
<Map center={center} zoom={zoom}>
<TileLayer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" />
<PolylineDecorator patterns={arrow} positions={polyline} />
</Map>
);
}
}
Here is a demo

React TinyMCE editor set bbcode content dynamically

I am using this official component https://www.tiny.cloud/docs/integrations/react/
I want to use this method from documentation https://www.tiny.cloud/docs/api/tinymce/tinymce.editor/#setcontent in order to define bbcode as content for my editor.
But I get an error:
this.editor.setContent is not a function
Here is my code
import React, { PureComponent } from 'react';
import { Editor } from '#tinymce/tinymce-react';
/**
* Comment component.
*/
class Comment extends PureComponent {
componentDidMount() {
this.editor.setContent('[b]some[/b] html', { format: 'bbcode' });
}
render() {
return (<Editor
ref={(editor) => {
this.editor = editor;
}}
apiKey="***"
/>);
}
}
export default Comment;
I managed to make TinyMCE editor to work with bbcode.
Here is my code:
import React, { PureComponent } from 'react';
import { Editor } from '#tinymce/tinymce-react';
/**
* Comment component.
*/
class Comment extends PureComponent {
constructor(props) {
super(props);
this.state = { content: 'this is a [url=https://google.com]link[/url]' };
this.handleEditorChange = this.handleEditorChange.bind(this);
}
handleEditorChange(content) {
this.setState({ content });
}
render() {
return (<Editor
value={this.state.content}
onEditorChange={this.handleEditorChange}
apiKey="***"
init={{
menubar: '',
plugins: 'bbcode link code',
toolbar: '',
}}
/>);
}
}
export default Comment;
in tinymce-react the right way to set initial content is:
<Editor
initialValue="<p>This is the initial content of the editor</p>"
/>
Ref: https://www.tiny.cloud/docs/integrations/react/#4replacetheappjsfile
As you can see from source the method you are seeking for is not exposed in Editor component.

react-codemirror2 has no CSS effect

I added react-codemirror2 to my project but it does not load the css although I import the codemirror.css file, because it is mentioned that css should be applied into the component somehow (mentioned here), but long story short it is still rendered like this:
Code Mirror with no CSS
I really don't know what the issue can be. So here is my code:
import { connect } from 'react-redux';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css';
import { Controlled as CodeMirror } from 'react-codemirror2';
require('codemirror/mode/xml/xml');
require('codemirror/mode/javascript/javascript');
class MyComponent extends Component {
handleValueChange = value => this.props.onUpdateValue({ value });
render() {
const { shade } = this.props;
const myOptions = {
mode: 'xml',
theme: shade === 'dark' ? 'material' : 'default',
lineNumbers: true,
}
return (
<CodeMirror
id="editor"
value={this.props.value}
options={myOptions}
onBeforeChange={(editor, data, value) => {
this.handleValueChange(value);
}}
onChange={(editor, data, value) => {}}
/>
);
}
}
function mapStateToProps(state) {
return {
shade: state.muiTheme.shade,
};
}
export default connect(
mapStateToProps,
null
)(MyComponent);
I also tried to #import the css file inside the global.css file of my project (like below) but nothing's changed.
#import '/node_modules/codemirror/lib/codemirror.css';
#import '/node_modules/codemirror/theme/material.css';
I really don't know what else should be tried or what am I doing wrong, because it shouldn't be something very special. So I'm asking you, and any suggestions would be helpful.
Thanks :)
I don't know why you met the problem, but it works for me.
import React, { Component } from "react";
// Import the code mirror component.
import { Controlled as CodeMirror } from "react-codemirror2";
// The following two imports is for the theme.
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css';
// This import is for the language syntax highlighting.
import 'codemirror/mode/javascript/javascript.js';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
src: ''
}
}
render() {
let option = {
mode: 'javascript',
theme: 'material'
};
return (
<div>
<Controlled
value={this.state.src}
option={option}
onBeforeChange={(editor, data, value) => {
this.setState({ src: value });
}}
onChange={(editor, data, value) => {}}
/>
</div>
)
}
}
Maybe the problem is here:
const myOptions = {
mode: 'xml',
theme: shade === 'dark' ? 'material' : 'default',
lineNumbers: true,
}
You should set the option object in rendering function before return, or set it in constructor() and attach it to this, like this.option = {}.
Okay this is something which may only happened to me but I post my solution, maybe someday someone have same issue.
As I said the problem was not loading the css, I don't know how others handle this issue but I have to copy all styles inside the node_modules/codemirror/lib/codemirror.css into a new css file and put it in some path inside my project. And inside the global.css file of my project I just imported that new created file like #import absolute/path/to/file/codemirror.css; and it worked at least for one case and one theme. I'm sure there is better ways to connect to all css files of codemirror but for now it did the basic thing that I needed.

Carousel built with ReactSwipeableViews does not show items

I am trying to implement this carousel using material-ui and react-swipeable-views.
I have a carousel item that looks like this:
import React, {Component, PropTypes} from 'react'
export default class CarouselItem extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
}
static defaultProps = {
href:'#'
}
constructor(props) {
super(props)
}
render() {
const carouselItemStyle = {
width:'100%',
height:'100%',
minHeight:'400px',
position:'absolute',
top:0,
left:0,
zIndex:-1,
opacity:1,
display:'block'
}
const {prepareStyles} = this.context.muiTheme
const {href,image} = this.props
debugger
return (<a href={href} style={prepareStyles(carouselItemStyle)}>
<img src={image}/>
</a>
)
}
}
I have a Carousel component that looks like this:
import React, {Component, PropTypes} from 'react'
import {v4} from 'node-uuid'
import CarouselItem from './CarouselItem'
import autoPlay from 'react-swipeable-views/lib/autoPlay'
import SwipeableViews from 'react-swipeable-views'
const AutoplaySwipeableViews = autoPlay(SwipeableViews)
export default class Carousel extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
}
static propTypes = {
items:PropTypes.arrayOf(PropTypes.string),
autoplay:PropTypes.bool
}
static defaultProps = {
autoplay:false
}
constructor(props) {
super(props)
}
render() {
const carousel = {
overflow:'hidden',
position:'relative',
width:'100%',
perspective:'500px',
transformStyle:'preserve-3d',
transformOrigin:'0% 50%'
}
const carouselSlider = {
top:0,
left:0,
height:0
}
const {style:customStyles} = this.props
const style = Object.assign(
carousel,
carouselSlider,
customStyles
)
const {prepareStyles} = this.context.muiTheme
const SwipeImplementation = this.props.autoplay?AutoplaySwipeableViews:SwipeableViews
debugger
const carouselItems = this.props.items.map(function(item){
debugger
return <CarouselItem key={v4()} href="#" image={item}/>
})
return (<div style={prepareStyles(style)}>
<SwipeImplementation>
{carouselItems}
</SwipeImplementation>
</div>
)
}
}
I use the Carousel like this:
const items = [
'http://estruct.com.au/wp-content/uploads/2014/10/old-gccc-logo.png',
'http://www.activehealthycommunities.com.au/wp-content/uploads/2014/07/City-of_Gold-Coast_stacked_CMYK-01.jpg'
]
return (
<Carousel items={items} autoplay={true} />
)
I find that the carousel items do not appear, when I look in the developer tools, I find that transitions are happening but I do not see the items.
I have created a webpackbin with the code
I get an error in the bin that I do not have in my dev environment.
UPDATE:
If I remove the style for the a tag and change it to a div within CarouselItem:
//style={prepareStyles(carouselItemStyle)}
return (<div><img src={image}/></div>)
The images are displayed but are not full width. I notice that the transform css as well as height are determined using jQuery. How can we establish proper styling for the CarouselItem.
I think the problem is with your helloWorld.js. You're not creating the component correctly. Switching it to this is rendering the images for me.
export default class HelloWorld extends React.Component {
render() {
const items = [
'http://estruct.com.au/wp-content/uploads/2014/10/old-gccc-logo.png',
'http://www.activehealthycommunities.com.au/wp-content/uploads/2014/07/City-of_Gold-Coast_stacked_CMYK-01.jpg'
]
return (
<MuiThemeProvider>
<Carousel items={items} autoplay={true}/>
</MuiThemeProvider>
);
}
}

Resources