Skip to content

Commit

Permalink
backport of PR #9627 for Draftjs update in Text Widget
Browse files Browse the repository at this point in the history
  • Loading branch information
mumairr committed Nov 22, 2023
1 parent ab67bcb commit 9f541bc
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 27 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
"draft-js-side-toolbar-plugin": "3.0.1",
"draftjs-to-html": "0.8.4",
"element-closest": "2.0.2",
"embed-video": "2.0.4",
"es6-object-assign": "1.1.0",
"es6-promise": "2.3.0",
"eventlistener": "0.0.1",
Expand Down
126 changes: 126 additions & 0 deletions web/client/components/mapviews/settings/CompactRichTextEditor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2022, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import { Editor } from 'react-draft-wysiwyg';
import embed from 'embed-video';
import { DEFAULT_FONT_FAMILIES } from '../../../utils/GeoStoryUtils';

export const resizeBase64Image = (src, options) => {
return new Promise((resolve, reject) => {
const {
size,
type = 'image/png',
quality = 0.9
} = options || {};
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
const { naturalWidth, naturalHeight } = img;
const imgResolution = naturalWidth / naturalHeight;
const width = size;
const height = size / imgResolution;
const canvas = document.createElement('canvas');
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
const dataURL = canvas.toDataURL(type, quality);
resolve(dataURL);
};
img.onerror = (error) => {
reject(error);
};
img.src = src;
});
};

function CompactRichTextEditor({
wrapperClassName = 'ms-compact-text-editor',
toolbarOptions,
...props
}) {

return (
<Editor
{...props}
editorStyle={{ minHeight: 200 }}
wrapperClassName={wrapperClassName}
toolbar={{
options: toolbarOptions || ['fontFamily', 'blockType', 'inline', 'textAlign', 'list', 'link', 'colorPicker', 'remove', 'image', 'embedded'],
image: {
urlEnabled: true,
// upload controlled via props, disabled by default
uploadEnabled: props.uploadEnabled || false,
alignmentEnabled: false,
uploadCallback: (file) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('load', () => {
resizeBase64Image(reader.result, {
size: 500,
type: 'image/jpeg',
quality: 0.8
}).then((linkBase64) => {
resolve({ data: { link: linkBase64 } });
});
});
if (file) {
reader.readAsDataURL(file);
} else {
reject();
}
}),
previewImage: true,
inputAccept: 'image/gif,image/jpeg,image/jpg,image/png,image/svg',
alt: props.alt || { present: false, mandatory: false },
defaultSize: {
height: 'auto',
width: '100%'
}
},
fontFamily: {
// Setup fonts via props or use default from GeoStories
options: props.fonts || DEFAULT_FONT_FAMILIES
},
link: {
inDropdown: false,
showOpenOptionOnHover: true,
defaultTargetOption: '_self',
options: ['link', 'unlink']
},
blockType: {
inDropdown: true,
options: ['Normal', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Blockquote', 'Code']
},
inline: {
inDropdown: true,
options: ['bold', 'italic', 'underline', 'strikethrough', 'monospace']
},
textAlign: {
inDropdown: true
},
list: {
inDropdown: true
},
embedded: {
embedCallback: link => {
const detectedSrc = /<iframe.*? src="(.*?)"/.exec(embed(link));
return (detectedSrc && detectedSrc[1]) || link;
},
defaultSize: {
height: 'auto',
width: '100%'
}
}
}}
/>
);
}

export default CompactRichTextEditor;
84 changes: 57 additions & 27 deletions web/client/components/widgets/builder/wizard/text/TextOptions.jsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,69 @@
/*
/**
* Copyright 2018, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import { Col, Form, FormControl, FormGroup } from 'react-bootstrap';
import ReactQuill from '../../../../../libs/quill/react-quill-suspense';
import React, { useState } from "react";
import { Col, Form, FormControl, FormGroup } from "react-bootstrap";
import localizedProps from "../../../../misc/enhancers/localizedProps";
import {
htmlToDraftJSEditorState,
draftJSEditorStateToHtml
} from "../../../../../utils/EditorUtils";

import localizedProps from '../../../../misc/enhancers/localizedProps';
import withDebounceOnCallback from "../../../../misc/enhancers/withDebounceOnCallback";
import CompactRichTextEditor from "../../../../mapviews/settings/CompactRichTextEditor";

const TitleInput = localizedProps("placeholder")(FormControl);
const DescriptorEditor = withDebounceOnCallback(
"onEditorStateChange",
"editorState"
)(CompactRichTextEditor);

const Editor = localizedProps("placeholder")(ReactQuill);

export default ({ data = {}, onChange = () => { }}) => (
<div>
<Col key="form" xs={12}>
<Form>
<FormGroup controlId="title">
<Col sm={12}>
<TitleInput style={{ marginBottom: 10 }} placeholder="widgets.builder.wizard.titlePlaceholder" value={data.title} type="text" onChange={e => onChange("title", e.target.value)} />
</Col>
</FormGroup>
</Form>
</Col>
<Editor modules={{
toolbar: [
[{'size': ['small', false, 'large', 'huge'] }, 'bold', 'italic', 'underline', 'blockquote'],
[{'list': 'bullet' }, {'align': [] }],
[{'color': [] }, {'background': [] }, 'clean'], ['image', 'link']
]
}} placeholder="widgets.builder.wizard.textPlaceholder" value={data && data.text || ''} onChange={(val) => onChange("text", val)} />
</div>
);
function TextOptions({ data = {}, onChange = () => {} }) {
const [editorState, setEditorState] = useState(
htmlToDraftJSEditorState(data.text || "")
);

return (
<div>
<Col key="form" xs={12}>
<Form>
<FormGroup controlId="title">
<Col sm={12}>
<TitleInput
style={{ marginBottom: 10 }}
placeholder="widgets.builder.wizard.titlePlaceholder"
value={data.title}
type="text"
onChange={(e) =>
onChange("title", e.target.value)
}
/>
</Col>
</FormGroup>
</Form>
</Col>
<DescriptorEditor
editorState={editorState}
onEditorStateChange={(newEditorState) => {
const previousHTML = draftJSEditorStateToHtml(editorState);
const newHTML = draftJSEditorStateToHtml(newEditorState);
if (newHTML !== previousHTML) {
onChange(
"text",
draftJSEditorStateToHtml(newEditorState)
);
setEditorState(newEditorState);
}
}}
// Array of custom or built in fonts can be set via props
// fonts={["Arial", "Impact", "Roman"]}
/>
</div>
);
}
export default TextOptions;
1 change: 1 addition & 0 deletions web/client/plugins/WidgetsBuilder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class SideBarComponent extends React.Component {
size={this.props.dockSize}
zIndex={this.props.zIndex}
position={this.props.position}
className="widgets-builder"
bsStyle="primary"
hideHeader
style={{...this.props.layout, background: "white"}}>
Expand Down
20 changes: 20 additions & 0 deletions web/client/themes/default/less/common.less
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,26 @@
}
}

// forced to avoid conflicts with no-image display for not found at link
.rdw-link-decorator-icon
{
width: 15px !important;
min-width: 10px !important;
min-height: 15px !important;
position: static !important;
vertical-align: text-top;
}

.widgets-builder .ms2-border-layout-content {
position: relative;
overflow: auto;
}

.rdw-link-modal,
.rdw-embedded-modal
{
height: fit-content !important;
}
// **************
// Layout
// **************
Expand Down

0 comments on commit 9f541bc

Please sign in to comment.